目录
浅拷贝
深拷贝
赋值运算符重载的深拷贝
在学习C++类和对象时我们学习了浅拷贝,本期我们将再次回顾浅拷贝并为大家讲述深拷贝的概念。
浅拷贝
在学习类和对象时我们学习了拷贝构造函数的概念,而且我们也知道,因为拷贝构造函数属于类的默认成员函数吗,所以当我们没有显示定义拷贝构造函数时,编译器会生成拷贝构造函数,编译器生成的拷贝构造函数对内置类型成员变量会完成字节序的值拷贝,对于自定义类型会去调用其拷贝构造函数。这里对内置类型成员变量进行的字节序值拷贝我们就称作浅拷贝,为什么我们我们要称字节序的值拷贝为浅拷贝呢?
我们通过依然自定义的string类中的拷贝构造函数为大家讲解:
代码如下:
class string
{
public:
string(const char* str)
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
~string()
{
delete [] _str;
_str = nullptr;
}
private:
char* _str;
};
void test1()
{
string s1("hello yjd");
//调用拷贝构造函数
string s2(s1);
//调用赋值运算符重载
string s3("hello world");
s3 = s1;
}
如上代码,我们自己定义了一个string类,string类由于我们没有声明拷贝构造函数,所以编译器会生成默认的拷贝构造函数,但是生成的默认的拷贝构造函数会对内置类型进行值拷贝,就会将s1对象的_str成员变量的值传给s2对象的_str成员变量。
所以就会导致下面这种情况,图示如下:
运行截图如下:
不难发现,编译器提示我们出现了错误,这是因为编译器默认生成的拷贝构造函数对于内置类型是值拷贝,所以s1._str的值会赋给s2._str,就会导致两个指针变量指向了统一块动态申请的空间,既然是动态申请的空间在最后为了防止内存泄漏肯定是要使释放的,因为两个对象在小销毁之前都会调用析构函数进行资源的清理,所以这就会导致统一块空间被释放了两次,这边会导致错误,所以为了防止这种情况的出现,在此情境下诞生了深拷贝。
深拷贝
什么是深拷贝,说白了深拷贝就是为了解决在堆上申请的空间被释放两次的问题,针对上述浅拷贝所产生的现象,为了防止两个指针指向了同一块空间,我们可以先让s2在堆上申请与s1大小相同的空间,在C语言中我们学习了字符串函数strcpy,我们可以通过这个函数实现两个字符串值的传递。
图示如下:
通过图示,不难发现,两个对象的成员变量指向了两个不同的空间,所以此时我们也就不用担心在堆区开辟的空间被释放两次的问题,所以深拷贝就解决了浅拷贝中所带来的不足。
代码如下:
string(string& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
上述代码便是我们自己生成的拷贝构造函数代码,所以在今后学的的数据结构只要需要在堆上动态开辟空间,我们就需要自己定义拷贝构造函数。
赋值运算符重载的深拷贝
在学习类和对象时我们也学习了赋值运算符重载,赋值运算符重载和拷贝构造函数的区别是:
1.拷贝构造函数是用一个已经存在的对象初始化一个刚刚创建的对象。
2.赋值运算符重载是两个已经存在的对象之间进行赋值。
图示如下:
解析:因为赋值运算符重载也属于类的成员函数,当我们没有显示定义赋值运算符重载时,编译器也会默认生成一个赋值运算符重载,但是默认生成的赋值运算符重载和默认生成的拷贝构造函数是类似的,对于内置类型的成员变量,会完成字节序的值拷贝(浅拷贝),通过默认拷贝构造函数我们知道,浅拷贝是存在弊端的,所以对于在堆上开辟空间的数据结构,我们必须自己显示定义赋值运算符重载。
其实整个过程和拷贝构造函数类似,不过赋值运算符重载我们要先释放掉原来的空间,释放掉原来的空间之后,在动态开辟一块与s1大小相同的空间,然后将这块空间的地址传给s3._str,然后通过strcpy函数实现字符串值的拷贝。
代码如下:
string& operator=(string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[]_str;
_str = tmp;
}
return *this;
}
截图如下:
注意,代码中我们有两个需要注意的地方:
1.自己不需要给自己赋值,所以直接返回自身即可。
2.为了避免申请空间失败仍然释放掉原来空间的这种情况,我们定义了一个中间变量tmp,当空间申请失败时,我们则不释放原来的空间,当空间申请成功时,我们先赋值给临时字符串tmp,最后再将临时字符串赋值给成员变量_str就可以完成赋值运算符重载。
好了,深拷贝和浅拷贝的内容到这里就全部结束了,我们本次为大家带来的是传统版本的深拷贝,后期会为大家讲述效率更高的的深拷贝的方法。
本期内容到此结束^_^