C++的一个类里面有6个默认成员函数,意味着如果我们自己不定义这六个函数,那么编译器会使用系统的成员函数,但是一旦我们自己定义了这六个成员函数,那么编译器会调用我们定义的,而不是系统默认的,现在我们主要来分析以下String的深浅拷贝问题。深浅拷贝是C++中比较重要的。
什么是浅拷贝
所谓的浅拷贝,就是只拷贝指针指向的内容,而不改变它的地址空间,这就意味着改变其中一个的内容,另一个也跟着改变。
示例:
class String
{
public:
String(char *str='\0') //构造函数
{
_str = new char[strlen(str)+1];
strcpy(_str, str);
}
String& operator=(const String& s);
~String()
{
if(_str)
{
delete[]_str;
_str = NULL;
}
}
private:
char *_str;
};
这里解释一下为什么构造函数缺省传参不传NULL而是‘\0’,其实想一下就知道了,如果我们传NULL,在后面的拷贝构造函数中难免用到它,一旦我们传了一个空指针过去,那么在这里就会出错,因此我们缺省传参传‘\0’,因为C中字符串以\0结尾,这里为了兼容C,因此传参传了一个\0。
我们看看浅拷贝的情况:
void Test()
{
String s1("hello world");
String s2(s1);
}
通过调试看看他们的存储情况
通过调试我们可以看到他们的内存位置,内容都是相同的,改变其中一个另一个也会随之改变,这就是浅拷贝。浅拷贝在我们析构的时候会带来很多问题,因为在一个对象生命周期结束之后就会调用析构函数,因此这就会导致内存泄露等问题,因此我们有了深拷贝。
深拷贝问题
深拷贝,就是拷贝的时候会另外申请一块空间,然后拷贝内容,对其中一块空间进行操作不会影响另一块空间。
深拷贝我们有两种方法:
(1)传统方法:老老实实进行申请空间,然后拷贝。
(2)现代方法:简单来说就是拿来主义,在别人开辟好空间,拷贝好数据,然后我们只需要拿过来就可以了。
(1)传统方法:
String& String::operator=(const String& s)
{
if(_str != s._str)
{
delete []_str; //释放原空间
_str = new char[strlen(s._str)+1];//申请内存
strcpy(_str, s._str);//拷贝
}
return *this;
}
(2)现代方法:
//拷贝构造
String(const String& s)
:_str(NULL) //思考一下为什么需要初始化为NULL??
{
String tmp(s);
swap(_str, tmp._str);
}
//赋值运算符重载
String& String::operator=(String s)
{
if(_str != s.str)
{
String tmp(s); //创建一个临时变量进行拷贝
swap(_str, tmp._str);// 交换数据
}
return *this;
}
思考:
为什么需要在拷贝构造的时候初始化_str为NULL,而重载不需要?
因为创建的tmp为临时空间,出了作用域后会被销毁,销毁后_str为野指针1,这是会导致最后的析构出错,因此需要赋初值为NULL。
在重载的时候创建的tmp也是一个临时变量,出了作用域后会调用析构函数,因为我们本来的意愿就是要释放这块空间,因此我们不用初始化。
现代方法与传统方法比较
现代方法更加简洁,在我们使用的时候通常两种配合着使用。