String类型是c++中非常重要的一个类型,标准库中有String类型,但使用时必须包含它的头文件 #include < String > ,现在我们要模拟String类,实现字符串拷贝。
一、浅拷贝
String::String(const String& s)//浅拷贝
:_str(s._str)
{}
int main()
{
String s1("1111");
String s2(s1);
return 0;
}
分析:
s1和s2指向同一块空间,析构时先释放s2,即释放掉这段空间,s1变为野指针,因此在释放s1时会出错,程序崩溃。总之,这段空间被释放了两次会出错。这就是浅拷贝的问题。
并且,当使用以下形式拷贝时,同样会出现问题:
int main()
{
String s1("1111");
String s2;
s2=s1;
return 0;
}
s1和s2分别开辟了一段空间,当时s2=s1时,s2指向s1这段空间,s2与自己开辟的空间断开,释放时会出错,程序崩溃。所以,我们同样要对“=”进行重载。
二、深拷贝
class String//深拷贝
{
public:
String(const char* pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)
:_pStr(new char[strlen(s._pStr)+1])//申请同样大小的空间
{
strcpy(_pStr,s._pStr);//将s1内容拷贝至s2
}
~String()
{
if(_pStr)
{
delete[] _pStr;
_pStr=NULL;
}
}
String& operator=(const String& s)
{
if(this!=&s)
{
char* str=new char[strlen(s._pStr)+1];//开辟同样s1大小的新空间
strcpy(str,s._pStr);//拷贝s1的内容到新空间
delete[] _pStr;//释放s2
_pStr=str;//让s2指向新空间
}
return *this;
}
private:
char* _pStr;
};
int main()
{
String s1("1111");
String s2(s1);
return 0;
}
测试时:
可以看到,s1、s2分别指向不同的空间,因此释放空间时不会出错。并且,深拷贝中对“=”的重载使程序支持链式访问,即s3=s2=s1.
三、简洁深拷贝
class String//简洁版深拷贝
{
public:
String(const char* pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)
:_pStr(NULL)
{
String str(s._pStr);
std::swap(_pStr,str._pStr);
}
~String()
{
if(_pStr)
{
delete[] _pStr;
_pStr=NULL;
}
}
String& operator=(const String& s)
{
if(this!=&s)
{
String str(s._pStr);
std::swap(_pStr,str._pStr);
}
return *this;
}
private:
char* _pStr;
};
简洁深拷贝实际只是将拷贝构造函数和赋值运算符重载写成了更现代的方法。
四、引用计数拷贝
原理:成员变量为_pStr,_pCount,每次赋值时,_pCount+1即又多了一个对象使用这段空间,所以,当每调用析构函数时,_pCount-1,当减至零时,表示没有对象使用这段空间,可以释放。
class String//引用计数拷贝
{
public:
String(const char* pStr="")
:_pCount(new int(1))
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)
:_pStr(s._pStr)
,_pCount(s._pCount)
{
++(*_pCount);
}
~String()
{
if(0==--(*_pCount) && _pCount)
{
delete[] _pStr;
delete _pCount;
_pStr=NULL;
_pCount=NULL;
}
}
String& operator=(const String& s)
{
if(_pStr!=s._pStr)
{
if(0==--(*_pCount))
{
delete[] _pStr;
delete _pCount;
}
_pStr=s._pStr;
_pCount=s._pCount;
++(*_pCount);
}
return *this;
}
private:
char* _pStr;
int* _pCount;//引用计数
};
五、写时拷贝cow:在写的时候才会开辟空间
class String//写时拷贝
{
public:
String(char* pStr="")
{
if(pStr==NULL)
{
_pStr=new char[1+4];
_pStr+=4;
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1+4];
_pStr+=4;//往后移动4字节
strcpy(_pStr,pStr);//拷贝内容
}
GetCount()=1;
}
String(const String& s):_pStr(s._pStr)
{
++GetCount();//每次调用计数+1
}
String& operator=(const String& s)
{
Release();//释放原来的空间
_pStr=s._pStr;
++GetCount();
return *this;
}
~String()
{
Release();
}
char& operator[](size_t index)
{
if(GetCount()>1)
{
char* temp=new char[strlen(_pStr)+1+4];
temp+=4;
strcpy(temp,_pStr);
--GetCount();//原来的计数减1
_pStr=temp;
GetCount()=1;//重新开辟的空间计数为1
}
return _pStr[index];
}
int& GetCount()//获取计数器次数
{
return *((int*)_pStr-1);//向前移动了4字节
}
void Release()//释放空间
{
if(0==--GetCount())//1人使用时,删除
{
_pStr-=4;//指针向前移4字节
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
};