IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 177-C++基础(对象的浅拷贝和深拷贝,拷贝构造和赋值重载) -> 正文阅读

[C++知识库]177-C++基础(对象的浅拷贝和深拷贝,拷贝构造和赋值重载)

1、this指针、构造,析构回顾

this指针:

  • 一个类有很多对象,每个对象独自的成员变量,共享一套成员方法;
  • 成员方法,一经编译,方法的参数都会添加一个this指针,接收调用该方法的对象。
构造函数:
	定义对象时,自动调用的;可以重载的;构造完成,对象产生了
析构函数:
	不带参数,不能重载,只有一个析构函数;析构完成,对象就不存在了
.data上的对象  定义的时候构造,程序结束时析构
heap上的对象  new的时候构造 delete的时候析构
stack上的对象  进入函数到它定义的地方构造,出函数作用域析构

2、浅拷贝

  • 没有提供任何构造函数的时候,会为你生成默认构造和默认析构,是空函数;
  • 当你提供一个构造函数的时候,就不会提供默认的构造了 ;
  • 如果我们没有提供拷贝构造,调用的是默认的拷贝构造函数-》是做直接内存的数据拷贝

在这里插入图片描述

在这里插入图片描述

  • 在调用第二个析构函数的时候崩溃了
  • 系统提供的默认的拷贝构造函数做的是内存的拷贝—浅拷贝

在这里插入图片描述

  • s1的指针还是指向中间那块内存,但是中间那块内存已经被释放了,所以s1的这个指针已经成为了野指针。
  • 因此,s2析构完到s1析构的时候,成了释放野指针的操作了,程序崩溃。

在这里插入图片描述
默认的拷贝构造函数如下:(浅拷贝)
在这里插入图片描述

我们应该做深拷贝:

  • 对象的成员变量不仅仅要把值给它;
  • 如果对象的指针指向了外部资源,你应该给这个对象也单独开辟一个外部资源,让新对象的指针指向它,每个对象的指针指向自己独有的外部资源,析构时互不干扰!

在这里插入图片描述

经验:

  • 在oop编程时,对象的成员变量有指针,构造时,发现指针指向外部堆内存, 这个对象默认构造时,一定会出现浅拷贝的问题的;
  • 此时就不能依赖编译器产生的默认拷贝构造函数(它只会进行直接内存数据拷贝),自己写拷贝构造函数!

3、深拷贝

我们应该做深拷贝:

  • 对象的成员变量不仅仅要把值给它;
  • 如果对象的指针指向了外部资源,你应该给这个对象也单独开辟一个外部资源,让新对象的指针指向它,每个对象的指针指向自己独有的外部资源,析构时互不干扰!

在这里插入图片描述
我们要自定义拷贝构造函数(深拷贝),因为对象的浅拷贝有问题。

//自定以拷贝构造函数 =》深拷贝
SeqStack(const SeqStack& src)
{
	cout << "SeqStack(const SeqStack &src)" << endl;
	_pstack = new int[src._size];
	for (int i = 0; i <= src._top; ++i)
	{
		_pstack[i] = src._pstack[i];
	}
	_top = src._top;
	_size = src._size;
}

这样,对象就是各自析构自己的外部资源了!
在这里插入图片描述
现在就没有问题了!

4、扩容的时候为什么要用for循环,不用memcpy?

  • 因为在进行数据的拷贝的时候,我们把内存上的数据拷贝到另一块内存上;
  • 如果内存上放的是整型数据,每一个整型不占用整型之外的资源,我们使用memcpy拷贝字节过来,是没有问题的。
  • 但是,我们假设这个数组里面放的不是整型,而是对象,每一个对象都有指针,指向外部的资源,则这个数组里的对象的浅拷贝是有问题的,用memcpy只是把对象本身的内存拷贝一份,调用realloc也是如此(memcpy和realloc做的都是浅拷贝),造成指针指向的都是同一块外部资源,在析构的时候,这个外部资源要析构2次,造成程序崩溃。
  • 除非明确拷贝的数据,没有占用外部资源,则可以使用memcpy方法或者realloc,否则我们一定要用for循环去解决问题。
    在这里插入图片描述

5、赋值重载函数

在这里插入图片描述

在这里插入图片描述

  • 赋值操作,s1,s2都是存在的对象。
  • 我们现在没有给类提供赋值操作,系统产生默认的赋值函数,也是做直接的内存拷贝!会在第二次析构的时候出现问题!

在这里插入图片描述

  • 又是浅拷贝,而且还把s2原本的资源给丢了

  • 我们得把s2原本指向的内存释放掉,然后根据s1的尺寸重新开辟一个堆内存,自己的指针指向自己的外部资源。

当对象浅拷贝有问题的时候,不仅要定义拷贝构造函数,还需要定义赋值重载函数

在这里插入图片描述

	//自定义赋值重载函数 s2 = s1;
	void operator=(const SeqStack& src)
	{
		cout << "operator=" << endl;
		//防止自赋值	s1 = s1;
		if (this == &src)
			return;

		//需要先释放当前对象占用的外部资源
		delete[]_pstack;

		_pstack = new int[src._size];
		for (int i = 0; i <= src._top; ++i)
		{
			_pstack[i] = src._pstack[i];
		}
		_top = src._top;
		_size = src._size;
	}

6、代码汇总

#include <iostream>
using namespace std;


class SeqStack
{
public:

	//构造函数 
	SeqStack(int size = 10)
	{
		cout << this << " SeqStack()" << endl;
		_pstack = new int[size];
		_top = -1;
		_size = size;
	}

	//自定以拷贝构造函数 =》深拷贝
	SeqStack(const SeqStack& src)
	{
		cout << "SeqStack(const SeqStack &src)" << endl;
		_pstack = new int[src._size];
		for (int i = 0; i <= src._top; ++i)
		{
			_pstack[i] = src._pstack[i];
		}
		_top = src._top;
		_size = src._size;
	}

	//自定义赋值重载函数 s2 = s1;
	void operator=(const SeqStack& src)
	{
		cout << "operator=" << endl;
		//防止自赋值	s1 = s1;
		if (this == &src)
			return;

		//需要先释放当前对象占用的外部资源
		delete[]_pstack;

		_pstack = new int[src._size];
		for (int i = 0; i <= src._top; ++i)
		{
			_pstack[i] = src._pstack[i];
		}
		_top = src._top;
		_size = src._size;
	}


	//析构函数
	~SeqStack()
	{
		cout << this << " ~SeqStack()" << endl;
		delete[]_pstack;
		_pstack = nullptr;
	}

	void push(int val)
	{
		if (full())
			resize();
		_pstack[++_top] = val;
	}
	void pop()
	{
		if (empty())
			return;
		--_top;
	}
	int top()
	{
		return _pstack[_top];
	}
	bool empty() { return _top == -1; }
	bool full() { return _top == _size - 1; }
private:
	int* _pstack;//动态开辟数组,存储顺序栈的元素
	int _top;//指向栈顶元素的位置
	int _size;//数组扩容的总大小

	void resize()
	{
		int* ptmp = new int[_size * 2];
		for (int i = 0; i < _size; ++i)
		{
			ptmp[i] = _pstack[i];
		} //memcpy(ptmp, _pstack, sizeof(int)*_size); realloc
		delete[]_pstack;
		_pstack = ptmp;
		_size *= 2;
	}
};
int main()
{
	SeqStack s;//没有提供任何构造函数的时候,会为你生成默认构造和默认析构,是空函数
	//当你提供一个构造函数的时候,就不会提供默认的构造了 
	SeqStack s1(10);
	SeqStack s2 = s1;// #1 如果我们没有提供拷贝构造,调用的是默认的拷贝构造函数-》是做直接内存的数据拷贝
	//SeqStack s3(s1);// #2 #1和#2这两种写法是一样的 
	
	s2 = s1;	//默认赋值函数,直接做内存的拷贝
	return 0;
}

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 11:40:56  更:2022-05-10 11:43:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 20:41:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码