今天看到剑指offer上的一道题:
如下为类型CMyString的声明,请你为该类型添加赋值运算符函数
class CMyString
{
public:
CMyString(char * pData = NULL);
CMyString(const CMyString & str);
~CMyString(void);
private:
char * m_pData;
};
打算研究一下这道题,忽然又想起来以前有写过带有写时拷贝的String类的实现,所以准备先回顾一下以前的东西,再解决这道题。
1.浅拷贝:
在实现String类的时候,如果没有自己实现赋值函数或拷贝构造函数,使用默认赋值函数或拷贝构造函数会以浅拷贝实现赋值函数,这样就很有可能在释放的时候造成程序的崩溃。
因为是浅拷贝,所以两个对象的str指针同时指向了一块空间,当这两个对象生命周期结束时,都会调用析构函数,那么这一块空间就被析构了两次,所以会崩溃;
还存在一个问题,如果改变str1对象的指针指向的空间,由于两对象的指针指向同一块空间,所以str2对象的指针所指向的空间也一块变了,这不是我们想看到的。
2. 深拷贝:
为了解决浅拷贝的问题,可以采用深拷贝来实现赋值函数或拷贝构造函数。
深拷贝的时候,虽然解决了浅拷贝中存在的问题,但是又出现了新的问题:每一次在进行深拷贝的时候,都需要进行拷贝数据,代价过大。如果我们新拷贝出来的数据只需要进行读操作,而不需要进行写操作,这样也会浪费内存空间。
3.写时拷贝:
什么是写时拷贝,就是写的时候才拷贝,也就是说,你如果要改变,就需要拷贝一份,改变的是拷贝的这一份。写时拷贝是通过增加一个引用计数确定是否需要进行拷贝。这其中的改变包括 * 、[ ] 运算。 所以我们还需要重载这两个运算符,同时输出流运算符也是需要重载的。
加了引用计数后:
加了写时拷贝的String类的实现如下:
//String类的写时拷贝的实现
class Mstring
{
public:
//构造函数的实现
//加引用计数的string前四个字节存放四个字节的整型,作为引用计数
Mstring(const char *str = NULL)//NULL = '\0'
{
if (NULL == str)
{
_str = new char[1 + 4];//多申请四个字节,存放引用计数
getNum() = 1; //初始化引用计数为1
*(getHead()) = 0;
_len = 0;
return;
}
_str = new char[strlen(str) + 1 + 4];
getNum() = 1;
_len = strlen(str);
strcpy_s(getHead(), _len + 1, str); //
}
//拷贝构造函数的实现
Mstring(const Mstring &src)
{
_str = src._str;
_len = src._len;
getNum()++; //引用计数 +1
}
//重载 = 运算符
Mstring& operator=(const Mstring& src)
{
if (&src == this) //防止自赋值
{
return *this;
}
Mdelete();
_str = src._str;
_len = src._len;
getNum()++;
return*this;
}
//重载解引用运算符
char &operator*()
{
if (getNum() == 1)
{
return *getHead();
}
char* tmp = new char[_len + 1 + 4];
strcpy_s(tmp + 4, _len + 1, getHead());
getNum()--;
_str = tmp;
getNum() = 1;
return *getHead();
}
//重载 [] 运算符
char& operator[](int sit)
{
if (getNum() == 1)
{
return getHead()[sit];
}
char*tmp = new char[_len + 1 + 4];
strcpy_s(tmp + 4, _len + 1, getHead());
getNum()--;
_str = tmp;
getNum() = 1;
return getHead()[sit];
}
//重载 输出流运算符
ostream& operator << (ostream& out)
{
out << getNum() << " ";
out << getHead() << endl;
return out;
}
//析构函数 调用自己实现的Mdelete
~Mstring()
{
Mdelete();
}
private:
int& getNum()//获取当前的引用计数
{
return *((int *)_str);
}
char* getHead()//获取字符串的首元素
{
return _str + 4;
}
void Mdelete()//自己实现delete
{
if (--getNum() == 0)
{
delete _str;
}
}
char*_str; //字符串
int _len; //字符串长度
};
int main()
{
Mstring str1 = "hello";
str1.operator<<(cout);
Mstring str2 = str1;
str1.operator<<(cout);
str2.operator<<(cout);
Mstring str3;
str3.operator<<(cout);
str3 = str1;
str1.operator<<(cout);
str2.operator<<(cout);
str3.operator<<(cout);
cout << "==========================" << endl;
//str3[2] = 'm';
*str2 = 'm';
str1.operator<<(cout);
str2.operator<<(cout);
str3.operator<<(cout);
return 0;
}
实现了上面的代码,就能很容易的解决最开始的问题:
CMyString& operator =(const CMyString & str)
{
if (this == &str)
{
return *this;
}
//释放原内存区域,防止内存泄漏
delete[]m_pData;
m_pData = NULL;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}