String类浅拷贝,深拷贝,引用计数,写时拷贝
String类:标准库类型string类表示可变长的字符序列,定义在std中,专门用来管理字符串
浅拷贝
浅拷贝,是指原对象与拷贝对象公用一份实体,仅仅是对象名字不同而已,其中任何一个对象改变都会导致其他的对象也跟着它变。如下面这段代码:
#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:采用 指针 计数
#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);
cout << "String" << endl;
}
_count++;
}
//s2(s1)
String(const String& s) //拷贝构造函数
{
_pstr = s._pstr;
s._count++;
_count = s._count;
cout << "Stirng kaobei" << endl;
}
~String()
{
if (NULL == _pstr)
{
return;
}
else
{
cout << "~String" << endl;
if (--_count == 0)
{
delete[] _pstr;
_pstr = NULL;
}
}
}
String& operator=(String& s) //运算符重载
{
if (_pstr != s._pstr)
{
cout << "operator=" << endl;
_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;
}
空间被准确释放,指针计数看起来是很完美的操作 ,然而还是有瑕疵
比如:每个对象为指针计数多创建一个指针,浪费空间,还有释放麻烦,有可能我们只记得释放_pstr,却忘记释放计数器指针的空间,造成内存泄漏。
写时拷贝
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;
}
深拷贝
所谓深拷贝,就是为新对象开辟一块新的空间,并将原对象的内容拷贝给新开的空间,释放时就不会牵扯到多次析构的问题
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;
}