C++——string的深拷贝与浅拷贝

时间:2022-04-08 21:58:12

在c++中,基本所有的类都要考虑深拷贝,浅拷贝与写时拷贝,根据不同的定义,选择适合自己的拷贝方式。时间类就可以用浅拷贝,而二叉树,string类就需要深拷贝。
string类在vs编译器下使用的深拷贝,在Linux下使用的浅拷贝。
为什么会存在深浅拷贝的问题呢?
string的浅拷贝是让两个不同的指针指向同一块空间,而这在析构的时候会出现将一块空间释放两次,程序会崩溃,因此我们才需要进行深拷贝,即第二个指针开辟和第一个指针一样大小空间,然后将内容复制过去。
深拷贝:

class string
{

public:
string(char* pstr) //构造函数
:str(new char[strlen(pstr) + 1]) //开辟新的空间
{
if (pstr == NULL)//
{
str = new char[1]; //开辟一个 存放‘\0’
*str = '\0';
}
else
{
str = new char[strlen(pstr) + 1];
for (size_t i = 0; i < strlen(pstr); i++) //深复制
{
str[i] = pstr[i];
}
//strcpy(str,pstr);
//memcpy(str,pstr,strlen(pstr)+1);浅复制
}
}
//拷贝构造函数
string(const string& pstr)
:str(new char[strlen(pstr.str)+1])
{
for (size_t i = 0; i < strlen(pstr.str); i++)
{
str[i] = pstr.str[i];
}
//strcpy(str,pstr.str);
//memcpy(str,pstr.str,strlen(pstr.str)+1);
}
//赋值运算符重载
string& operator = (const string & pstr)
{
if (&str == &pstr.str) //检查是否是自己给自己赋值
return *this;
delete[] str; //释放临时空间
str = new char[strlen(pstr.str) + 1];
for (size_t i = 0; i < strlen(pstr.str); i++)
{
str[i] = pstr.str[i];
}
//strcpy(str,pstr.str);
//memcpy(str,pstr.str,strlen(pstr.str)+1);
return *this;
}
/*现代写法: 根据拷贝构造函数让系统自己开辟空间
拷贝构造函数
string(const string& pstr)
:str=null; 必须置为空,要不然访问地址非法化
{
string tmp (pstr);
swap(tmp.str,str);
return *this;
}
赋值运算符重载
string& operator = (const string& pstr )
{
string tmp(pstr);
swap(tmp.str, str);
return *this;
}*/

//析构函数
~string()
{
delete[] str;
str = NULL;
}

private:
char* str;

};
int main()
{
string a ("12345");
string b(a);
cout << b << endl;
}

浅拷贝,当我们需要改变新的空间的内容的时候,才会重新开辟空间呢?
1)判断空间使用的次数来选择析构,增加一个类成员 count,但是这样造成的后果是每一个成员都有一个不同的count 在析构的时候就很混乱还是会出错
2)然后呢我们会想到使用静态成员的办法,因为此时 static int count 是放在静态区,它是所有对象共享的,不会为每个对象单独生成一个count,可是当我们有多个不同的成员共同管理一块空间,而此时我们又用构造函数创建一个对象时候,count又会变为1,所以这种方法还是不行 。
3)使用引用计数,把引用计数放在字符串的前四个字节里,每个创建出来的对象都有不同的引用计数

class string
{

public:
string(char* pstr) //构造函数
:str(new char[strlen(pstr) + 1+4])//多开辟的4个字节存放引用计数
{
if (pstr == NULL)
{
str = new char[1];
*str = '\0';
}
else
{
str = new char[strlen(pstr) + 1+4];
int* count = (int* )str;
*count = 1;
count ++; //后移拷贝数据
str = (char*)count;
for (size_t i = 0; i < strlen(pstr); i++) //深复制
{
str[i] = pstr[i];
}
//strcpy(str,pstr);
//memcpy(str,pstr,strlen(pstr)+1);浅复制
}
}
//拷贝构造函数
string(const string& pstr)
{
str = pstr.str;
int * count = (int *)(str-4) ;
++*count;

}
//赋值运算符重载
string& operator = (const string & pstr)
{
if (&str == &pstr.str)
return *this;
destroy();//判断是否释放空间
str = pstr.str;
int * count = (int *)(str-4) ;
++*count;
}
void destroy()
{
int * count = (int *)(str-4); //获取引用计数
if(*count == 1
{
delete[](str-4);
str = NULL;
}
else
--*count;
}
//析构函数
~string()
{
destroy();
}

private:
char* str;
int * count;
};

写时拷贝:
在浅拷贝中,假设有多个名称引用了同一个字符串,当其中一个需要修改字符串时,这就会导致引用的值发生改变,这明显不是我们所期望的,所以出现了写时拷贝,(可能会修改字符串的时候使用深拷贝将其与大部队分离,这样就保护了其他引用成员)
则我们只需要重载[] 就可以实现

const string& operator[]const 
{
string tmp(pstr);
swap(tmp.str, str);
return *this;
}