String类时C++中非常重要的一个类,模拟实现String类涉及到浅拷贝,深拷贝的问题。
浅拷贝:在拷贝的时候,只是拷贝了指针的内容,而指针所指向的空间并没有变,两个指针指向同一块内存空间。在释放空间的时候,就会造成重复释放同一块内存空间而报错。
深拷贝:在拷贝的时候,开辟一块和原指针指向的空间相同大小的空间,使新增加的指针指向新的内存,这样在释放空间时,就不会出现重复释放了。
引用计数:前拷贝出现的问题是在拷贝构造函数及赋值运算符的重载,因此我们增加一个指针,当管理该空间的指针增加一个,我们将让这个变量增加1,当变量减为0时,我们再释放该空间。
由于在实现String类时浅拷贝存在问题,在此我们就直接用深拷贝和引用计数的方法实现String类
class String
{
public:
friend ostream& operator<<(ostream& _cout, const String& s)
{
_cout << s._pStr;
return _cout;
}
String(const char* pStr = "")
{
if (pStr == NULL)
{
_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);
}
~String()
{
if (_pStr)
{
delete[] _pStr;
_pStr = NULL;
}
}
String& operator=(const String& s)
{
if (this != &s)
{
char* Temp = new char[strlen(s._pStr) + 1];
strcpy(Temp, s._pStr);
delete[] _pStr;
_pStr = Temp;
}
return *this;
}
bool operator>(const String& s)//重载>
{
char* s1 = _pStr;
char* s2 = s._pStr;
while (*s1 == *s2)
{
if (*s1 == '\0')
return false;
s1++;
s2++;
}
if (*s1 > *s2)
return true;
return false;
}
bool operator<(const String& s)重载<
{
char* s1 = _pStr;
char* s2 = s._pStr;
while (*s1 == *s2)
{
if (*s1 == '\0')
return false;
s1++;
s2++;
}
if (*s1 < *s2)
return true;
return false;
}
bool operator==(const String& s)重载==
{
char* s1 = _pStr;
char* s2 = s._pStr;
while (*s1 == *s2)
{
if (*s1 == '\0' && *s2 == '\0')
return true;
s1++;
s2++;
}
return false;
}
bool operator!=(const String& s)重载!=
{
char* s1 = _pStr;
char* s2 = s._pStr;
if (s1 == s2)
return false;
return true;
}
private:
char *_pStr;
}
关于拷贝构造函数及赋值运算符的重载,我们还有几种简洁的实现方法: //注意此种方法,一定要在开始时将_pStr置为空指针,否则,程序可能会崩溃。
//在VS2013中this->_pStr为0x0,所以不会出错,在VS2008中this->_pStr为0xcccccccc,会出错
String(const String& s)//如果this指针不为0,交换后,tmp变为野指针,析构时程序崩溃
: _pStr(NULL) //而我们将_pStr初值置为NULL,交换后tmp._pStr也为0,析构时不会delete,不会出现问题
{
String tmp(s._pStr);
swap(_pStr, tmp._pStr);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* Temp = new char[strlen(s._pStr) + 1];
strcpy(Temp, s._pStr);
delete[] _pStr;
_pStr = Temp;
}
return *this;
}
String& operator=(const String& s)//用s构造tmp对象,交换当前指针和tmp,返回*this
{
if (this != &s)
{
String tmp(s);//拷贝构造
swap(_pStr, tmp._pStr);
}
return *this;
}
String& operator=(const String& s)
{
if (this != &s)
{
String tmp(s._pStr);//构造函数
swap(_pStr, tmp._pStr);
}
return *this;
}
String& operator=(String s)//值传递,拷贝构造构建临时变量
{
swap(_pStr, s._pStr);
return *this;
}
引用计数:class String
{
public:
String(const char* pStr = "")
:_count(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)
: _count(s._count)
, _pStr(s._pStr)
{
(*_count)++;
}
String& operator=(const String& s)
{
if (this != &s)
{
if (0 == --(*_count))//s3=s2
{
delete[] _pStr;
delete _count;
}
_pStr = s._pStr;//s2=s3
_count = s._count;
(*_count)++;
}
return *this;
}
~String()
{
if (0 == --(*_count) && _pStr)
{
delete[] _pStr;
_pStr = NULL;
delete _count;
_count = NULL;
}
}
private:
char* _pStr;
int * _count;
};
利用引用计数的方法,虽然也可以完成,但存在线程安全的问题。