#浅拷贝和深拷贝和写时拷贝的区别及原理**
1、浅拷贝
当出现类的等号赋值时,会调用拷贝函数
什么是浅拷贝?
在未定义显示拷贝构造函数时,系统默认调用拷贝构造函数(浅拷贝),够完成成员的复制。当数据成员中没有指针时,浅拷贝是可行的。
如果有指针,浅拷贝(只是拷贝了指针指向的地址),则类中的两个指针会指向同一块空间,当对象快结束时,会调用两次析构函数释放同一块空间,而导致指针悬挂现象。
所有,这时,必须采用深拷贝。
2、深拷贝
深拷贝和浅拷贝的区别
深拷贝会在堆内存中另外申请空间来存储数据,从而解决了指针悬挂的问题。
浅拷贝
浅拷贝,是指原对象与拷贝对象公用一份实体,仅仅是对象名字不同而已,其中任何一个对象改变都会导致其他的对象也跟着它变。如下面这段代码:
#include<iostream>
#include<cstring>
using namespace std;
class String
{
public:
String(const char* pstr = "")
:_pstr(new char[strlen(pstr)+1])//为字符串开辟空间
{
if (0 == *pstr) //如果字符串为空
{
*_pstr = '\0';
}
else //字符串不为空
{
strcpy(_pstr, pstr);
}
}
//s2(s1)
String(const String& s) //拷贝构造函数
{
_pstr = s._pstr;
}
// s3=s1
String& operator=(String& s) //运算符重载
{
if (_pstr != s._pstr)
{
_pstr = s._pstr;
}
return *this;
}
~String()
{
if (NULL == _pstr)
{
return;
}
else
{
delete[] _pstr;
_pstr = NULL;
}
}
private:
char* _pstr;
};
void Fun1()
{
String s1("abcd");
String s2(s1);
String s3 = s2;
String s4;
s4 = s3;
}
int main()
{
Fun1();
system("pause");
return 0;
}
其中s1,s2,s3,s4包含的指针对象同时指向一块内存,析构时delete了这个空间四次
可是代码并没有判断内存是否有效,会导致指针悬挂。
改进1:用一个计数器来控制析构函数
结果: 还是无法避免浅拷贝造成的内存泄漏问题
因为:1 四个对象本来指向同一块空间,计数器应该为4,现在结果却是计数器
只能控制与它相邻对象的计数器,对象创建完成后计数器并不统一
2 调用4次析构函数后,本来应该四个对象同时被释放,结果却没有一个
对象的计数器为0,也就是这块空间没有被释放
改进2 :采用 static计数器
#include<iostream>
#include<cstring>
using namespace std;
class String
{
public:
String(const char* pstr = "")
:_pstr(new char[strlen(pstr)+1])//为字符串开辟空间
{
if (0 == *pstr) //如果字符串为空
{
*_pstr = '\0';
}
else //字符串不为空
{
strcpy(_pstr, pstr);
}
_count++;
}
//s2(s1)
String(const String& s) //拷贝构造函数
{
_pstr = s._pstr;
s._count++;
_count = s._count;
}
~String()
{
if (NULL == _pstr)
{
return;
}
else
{
if (--_count == 0)
{
delete[] _pstr;
_pstr = NULL;
}
}
}
String& operator=(String& s) //运算符重载
{
if (_pstr != s._pstr)
{
_pstr = s._pstr;
s._count = _count;
_count++;
}
return *this;
}
private:
char* _pstr;
static int _count;
};
int String::_count = 0;
void Fun1()
{
String s1("abcd");
String s2(s1);
String s3 = s2;
String s4;
s4 = s3;
}
int main()
{
Fun1();
system("pause");
return 0;
}
结果:还是出现bug
1 我们创建了4个对象可是计数器却是5,因为静态成员变量为对象共享
任何对象都可用对它进行修改,每创建一个对象,我们对计数器加1,却忽略
创建的新对象是否与已经存在的对象占同一块空间
2 调用4次析构函数,计数器值为1,导致空间又没有被释放
改进3:采用 指针 计数
class String
{
public:
String(const char* pstr = "")//构造函数
:count(new int(0))
, _pstr(new char[strlen(pstr)+1])
{
if (NULL == pstr)
{
*_pstr = '\0';
}
else
{
strcpy(_pstr, pstr);
}
*count = 1;
}
String(const String& s)//拷贝构造
:count(s.count)
{
_pstr = (char*)s._pstr;
count = s.count;
(*count)++;
}
~String()//析构函数
{
if (NULL == _pstr)
{
return;
}
else
{
if (--(*count) == 0)
{
delete[]count;
delete[]_pstr;
_pstr = NULL;
count = NULL;
}
}
}
String& operator=(String& s)//赋值运算符重载
{
if (_pstr != s._pstr)
{
_pstr = s._pstr;
count = s.count;
(*count)++;
}
return *this;
}
private:
int* count;
char* _pstr;
};
void Funtest()
{
String s1("abcd");
String s2(s1);
String s3 = s2;//调用拷贝构造函数(编译器会s2直接初始化s3)
String s4;//s4对象已经存在了
s4 = s3;//编译器会调用赋值运算符重载将s3的值赋给s4
}
int main()
{
Funtest();
system("pause");
return 0;
}
空间被准确释放,指针计数看起来是很完美的操作 ,然而还是有瑕疵
比如:每个对象为指针计数多创建一个指针,浪费空间,还有释放麻烦,有可能我们只记得释放_pstr,却忘记释放计数器指针的空间,造成内存泄漏。
深拷贝
所谓深拷贝,就是为新对象开辟一块新的空间,并将原对象的内容拷贝给新开的空间,释放时就不会牵扯到多次析构的问题
class String
{
public:
String(const char* pstr = "")
:_pstr(new char[strlen(pstr)+1]) //开辟空间
{
if (0 == *_pstr) //如果是空内容
{
*_pstr = '\0';
}
else
{
strcpy(_pstr, pstr); //拷贝字符串
}
}
String(String& s) //拷贝构造函数
:_pstr(NULL) //防止交换后temp指向随机空间,本函数调用结束导致内存泄漏致崩溃
{
String temp(s._pstr);//如果不给出临时变量,交换后s的值将为NULL
std::swap(_pstr, s._pstr);
}
String& operator=(const String &s)//赋值运算符重载
{
if (_pstr != s._pstr) //防止自己给自己赋值
{
String temp(s._pstr); //如果不给出临时变量交换后的值为NULL
std::swap(_pstr, temp._pstr);
}
return *this;
}
~String()
{
if (NULL == _pstr)
{
return;
}
else
{
delete[]_pstr;
_pstr = NULL;
}
}
private:
char* _pstr;
};
void Funtest()
{
String s1("abcd");
String s2(s1);
String s3 = s2;//调用拷贝构造函数(编译器会s2直接初始化s3)
String s4;//s4对象已经存在了
s4 = s3;//编译器会调用赋值运算符重载将s3的值赋给s4
}
int main()
{
Funtest();
system("pause");
return 0;
}
写时拷贝
class String
{
public:
String(const char* pstr = "")
:_pstr(new char[strlen(pstr) + 4+1])//每次多开辟4个空间存放当前地址有几个对象
{
if (NULL == pstr) //如果当前字符为空
{
(*(int*)_pstr) = 1; //将前4个字节用来计数
_pstr += 4;
*_pstr = '\0';
}
else //字符串不为空
{
(*(int*)_pstr) = 1;//将前4个字节用来计数
_pstr += 4;//指针向后偏移4个字节
strcpy(_pstr, pstr); //将pstr内容拷贝到当前对象_pstr中
cout << "String" << endl;
}
}
//s2(s1)
String(const String& s) //拷贝构造函数
:_pstr(s._pstr)
{
++(*(int*)(_pstr - 4)); //向前偏移4个字节将计数加1
cout << "Stirng kaobei" << endl;
}
~String()
{
if (NULL == _pstr)
{
return;
}
else
{
cout << "~String" << endl;
if (--(*(int*)(_pstr-4)) == 0) //向前偏移4个字节判断计数是否为0,是0则是否
{
delete (_pstr-4);
_pstr = NULL;
}
}
}
String& operator=(String& s) //运算符重载
{
if (_pstr != s._pstr)
{
if (--(*(int*)(_pstr - 4)) == 0) //释放旧空间
{
delete (_pstr - 4);
_pstr = NULL;
}
_pstr = s._pstr; //指向新空间
++(*(int*)(_pstr - 4)); //计数加1
}
return *this;
}
private:
char* _pstr;
};
void Fun1()
{
String s1("abcd");
String s2(s1);
String s3 = s2;
String s4;
s4 = s3;
}
int main()
{
Fun1();
system("pause");
return 0;
}