1.浅拷贝。
当用一个已初始化过了的自定义类类型对象去初始化另一个对象的时候,拷贝构造函数就会被自动调用。也就是说,当需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
(1)一个对象以值传递的方式传入函数体
(2)一个对象以值传递的方式从函数返回
(3)一个对象需要通过另外一个对象进行初始化。
如果在写类时没有自己定义一个拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数,该对象函数完成对象之间的位拷贝,也就是我们说的浅拷贝。
class String
{
public:
/*不带参的默认构造函数*/
/*String()
:_str(new char[1])
{
*_str = '\0';
}*/
String(char *str="")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
~String()
{
delete[] _str;
}
private:
char* _str;
};
int main()
{
String str1("ABCD");
String str2(str1);
system("pause");
return 0;
}
让程序跑起来,发现程序崩了,这里看着没有什么问题啊,我们使用编译器自动给我们生成的拷贝构造函数。我们点开监视发现
字符串确实是拷贝进去了没问题,但是仔细看str1和str2的地址,我们发现str1和str2指的是同一块空间.
可是指向同一块空间为啥就崩了呢?
仔细想想 我们创建一个对象,最后在对象生命周期结束的时候,要调用构造函数,也就是要对创建的对象空间进行销毁。
知道这些我们再来看看反汇编
看这两个call指令,析构函数释放的是同一块空间的内存,编译器会先调用str2的析构函数,当我们delete[] str2后,str2所指向的这块内存就不存在了,然而我们刚才监视所得,str2与str1指向的同一块内存,然而我们已经通过str2的析构函数将这块内存释放掉了,所以这里的str1已经相当于已经是野指针了,当我们再调用str1的析构函数释放str1所指向的内存的时候程序必然会崩溃。
2.深拷贝.
这里浅拷贝只要的区别就是深拷贝会创建新内存把值全部拷贝一份就是深拷贝
下面是深拷贝构造的几种不同实现代码:
class String
{
public:
/*不带参的默认构造函数*/
/*String()
:_str(new char[1])
{
*_str = '\0';
}*/
String(char *str="")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
~String()
{
if(_str!=NULL)
{
delete[] _str;
_str=NULL;
}
String(const String& str)
:_str(new char[strlen(str._str)+1])
{
strcpy(_str, str._str);
}
//运算符重载
/*
原理重新申请一块比_str空间大一个的内存,把值拷贝到这个临时变量然后将原来_str空间释放掉,让_str指向新申请的这块空间
*/
String& operator=(const String& s)
{
if (this != &s)
{
char* pTemp = new char[strlen(s._str) + 1];
strcpy(pTemp, s._str);
delete[] _str;
_str = pTemp;
}
return *this;
}
private:
char* _str;
};
是不是觉得这个版本还挺麻烦的,那么有没有更简洁的版本呢?之后会写出简介版的string类。
/******************/
一个进击的学生
/*****************/