C++中的浅拷贝与深拷贝

时间:2021-01-21 19:51:01

用自定义的String类解释什么是浅拷贝什么是深拷贝。

class String 
{
private:
char* _str;
};
浅拷贝是在调用拷贝函数时进行了值拷贝,这样的拷贝看似没有问题,在调用析构函数时会导致内存泄漏,系统奔溃。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class String
{
public:
String(const char*str="")
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
cout << "String" << endl;
}
~String()
{
if (_str)
{
delete[]_str;
cout << "~String" << endl;
}
}
char *Getstr()
{
return _str;
}
private:
char*_str;
};
void TestString()
{
String s1("hello world!");
String s2;
String s3(s1);
//String s2 = s1;
cout << s1.Getstr() << endl;
cout << s3.Getstr() << endl;

}
int main()
{
TestString();
system("pause");
return 0;
}
我们自己编写了构造函数和析构函数,在运行测试用例时出现系统奔溃:

C++中的浅拷贝与深拷贝

是因为在String s3(s1)时系统调用默认拷贝构造即“浅拷贝”。

C++中的浅拷贝与深拷贝

所以我们要自己编写拷贝构造与赋值运算符重载,进行深拷贝。

深拷贝是指在拷贝构造时,新开辟一块空间,将_str指向的内容拷贝到这块新的空间里,再将自己的_str指向这块空间。下面是代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class String
{
public:
String(const char*str="")
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
cout << "String" << endl;
}
~String()
{
if (_str)
{
delete[]_str;
cout << "~String" << endl;
}
}
String(const String& d)
{
this->_str = new char[strlen(d._str) + 1]; //this可以省略,+1是因为字符串后还有‘\0’
strcpy(_str, d._str);
}

String& operator=(const String& d)
{
if (this != &d)
{
delete[]_str; //首先释放自己空间
_str = new char[strlen(d._str) + 1];
strcpy(_str, d._str);
}
return *this;
}
char *Getstr()
{
return _str;
}
private:
char*_str;
};
void TestString()
{
String s1("hello world!");
String s2;
String s3(s1);
s2 = s1;
cout << s1.Getstr() << endl;
cout << s3.Getstr() << endl;
cout << s2.Getstr() << endl;
}

运行结果为:

C++中的浅拷贝与深拷贝

深拷贝有两种写法,第一种是传统写法,就是我上面这种写法,老老实实开辟新空间,老老实实拷贝字符串;

还有一种是现代写法,相对于传统写法更简洁一点,是让别人开空间拷贝字符串,再将别人的空间与自己交换,坐享渔翁之利。

代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class String
{
public:
String(const char*str="")
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
cout << "String" << endl;
}
~String()
{
if (_str)
{
delete[]_str;
cout << "~String" << endl;
}
}
String(const String& d)
:_str(NULL) //如果tmp置空可以避免释放野指针
{
String tmp(d._str); //让临时对象tmp去构造与d._str相同的空间,再将tmp与自己交换
swap(_str, tmp._str);
}

String& operator=(const String& d)
{
if (this != &d)
{
String tmp(d._str); //赋值首先要做的是释放自己的空间,然后创建一块新空间,赋值为其他对象成员变量
swap(_str, tmp._str); //现代写法可以减少释放自己空间这一步骤,因为临时对象tmp在函数调用后自动释放
}
return *this;
}
char *Getstr()
{
return _str;
}
private:
char*_str;
};
void TestString()
{
String s1("hello world!");
String s2;
String s3(s1);
s2 = s1;
cout << s1.Getstr() << endl;
cout << s3.Getstr() << endl;
cout << s2.Getstr() << endl;


}
int main()
{
TestString();
system("pause");
return 0;
}
现代写法中的赋值运算符重载也可以使用对象参数,上面使用的是引用。

String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
在这种写法中,因为参数是一个临时变量,所以在调用赋值函数时String s就是对象的临时拷贝,可以直接交换,

String s因为是临时变量,在出了作用域后自动销毁,不用自己调用delete函数。