1. 什么是写时拷贝
写时拷贝故名思意:是在写的时候(即改变字符串的时候)才会真正的开辟空间拷贝(深拷贝),如果只是对数据的读时,只会对数据进行浅拷贝。
写时拷贝:引用计数器的浅拷贝,又称延时拷贝
:写时拷贝技术是通过"引用计数"实现的,在分配空间的时候多分配4个字节,用来记录有多少个指针指向块空间,当有新的指针指向这块空间时,引用计数加一,当要释放这块空间时,引用计数减一(假装释放),直到引用计数减为0时才真的释放掉这块空间。当有的指针要改变这块空间的值时,再为这个指针分配自己的空间(注意这时引用计数的变化,旧的空间的引用计数减一,新分配的空间引用计数加一)。
2. string中的两种写时拷贝
一:
- 动态开辟两个空间一个用来存放字符串:_str,一个用来存放计数器_refCountPtr
- 每次拷贝构造时(赋值运算符的重载后半段),直接把字符串的指针付给新的String,然后给计数器加加(*_refCountPtr)++
- 在释放String的时候,先对计数器减减,再判断计数器是否为零(即看是否还有指针共享此内存),若计数器为零则直接释放_str 和 _refCountPtr
- 在对字符串进行更改的时候(写时),就要进行深拷贝:先进行步骤3,在进行深拷贝和字符串的更改
class String//写时拷贝
{
public:
String(char * str = "\0")
:_refCountPtr(new int(1))//开辟计数器动态内存
,_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity+1];//开辟字符串动态内存
strcpy(_str,str);
}
String(String& s)//拷贝构造
:_str(s._str)//直接浅拷贝
,_refCountPtr(s._refCountPtr)
,_size(s._size)
,_capacity(s._capacity)
{
(*_refCountPtr)++;//但对计数器 ++
}
~String()//析构
{
Release();
}
inline void Release()
{
if(--(*_refCountPtr) == 0)//计数器--,并判断是否为0
{
cout<<"~String"<<_str<<endl;
delete[] _str;
delete _refCountPtr;//释放
}
}
String &operator=(String &s)//重载
{
if(_str != s._str)//判断是否为自己给自己赋值
{
Release();//判断并处理this
_str = s._str;
_refCountPtr = s._refCountPtr;
_size = s._size;
_capacity = s._capacity;
(*_refCountPtr)++;
}
return *this;
}
char *c_str() const
{
return _str;
}
//写时拷贝
String &push_back(char ch)
{
char* str = new char[_capacity*2];
strcpy(str,_str);
//先将原来的数据保存,用于给后面重新开辟的空间赋值
Release();//处理原来的空间
_str = str;
_str[_size++] = ch;
_str[_size] = '\0';
_capacity *= 2;
_refCountPtr = new int(1);//
return *this;
}
private:
char *_str;
int *_refCountPtr;
size_t _size;
size_t _capacity;
};
二:
开辟一个空间,前面4个字节为计数器count,剩下的为字符串_str的空间,用法与分开相同。
class String
{
public:
String(char *str = "\0")
:_capacity(strlen(str))
{
_size = _capacity;
_str = new char[_capacity + 5];//多开辟了4个字节
*(int *)_str = 1;//前4个自己存放计数器
_str += 4; //使_str指向开辟的4个字节后的空间
strcpy(_str,str);//拷贝字符串
}
String(String &s)//拷贝构造
:_str(s._str)//直接浅拷贝
,_capacity(s._capacity)
,_size(s._size)
{
++(*(int *)(_str-4));//(str前的4个字节为计数器) ++
}
~String()//析构
{
Release();
}
void Release()
{
if(--(*(int *)(_str-4)) == 0)//计数器-1判断是否为零
{
cout<<"~String "<<_str<<endl;
delete[] (_str-4);
}
}
String& operator=(String &s)//赋值重载
{
if(_str != s._str)//判断是否为自己为自己赋值
{
Release();//处理原指针(释放空间,或者计数器-1)
_str = s._str;
_size = s._size;
_capacity = s._capacity;//赋值
++(*(int *)(_str-4));//改变先指针的计数器
}
return *this;
}
String &operator+=(char ch)//重载,会改变,会深拷贝
{
char* str = new char[_capacity*2 + 6];
strcpy(str+4, _str);//先保存原字符串
Release();//因为要重新开辟空间,所以需要处理以前的空间
*(int *)str = 1;//新开辟的计数器置1
_str = str + 4;
_str[_size++] = ch;
_str[_size] = '\0';
_capacity *= 2;
return *this;
}
String &append(const char *str)//追加字符串
{
size_t len = strlen(str);//方法同+=的重载
_capacity = len + _size;
char *tmp = new char[_capacity + 6];
strcpy(tmp+4 ,_str);
Release();
*(int *)tmp = 1;
_str = tmp + 4;
strcpy(_str+_size, str);
_size = _capacity;
return *this;
}
private:
char* _str;//开辟多4个,指向4个字节后的地址
size_t _size;
size_t _capacity;
};