在模拟实现String类的过程中,不可避免的会遇到深拷贝浅拷贝的问题,下面就深拷贝浅拷贝做一个简介。
浅拷贝:
也称位拷贝,编译器只是直接将指针的值拷贝过来,结果多个对象共用同一块内存,当 一个对象将这块内存释放掉之后,另一些对象不知道该块空间已经还给了系统,以为还有效,所以在对这段内存进行操作的时候,发生了访问违规。
深拷贝:
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。将资源和值一块拷贝过来,此时两个对象各自占用资源,尽管值相同,但是互不影响。
//浅拷贝
class String
{
public:
String(const char* s = "")
{
if (NULL == s)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(s) + 1];
strcpy(_pStr, s);
}
}
String(const String& s)
{
_pStr = s._pStr;
}
String& operator=(const String& s)
{
if (this != &s)
{
_pStr = s._pStr;
}
return *this;
}
~String()
{
if (NULL != _pStr)
{
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
//深拷贝--普通版本
class String
{
public:
String(const char* s = "")
{
if (NULL == s)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(s) + 1];
strcpy(_pStr, s);
}
}
String(const String& s) : _pStr(new char[strlen(s._pStr) + 1])
{
strcpy(_pStr, s._pStr);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* temp = new char[strlen(s._pStr) + 1];
strcpy(temp, s._pStr);
delete[] _pStr;
_pStr = NULL;
_pStr = temp;
}
return *this;
}
~String()
{
if (NULL != _pStr)
{
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
//深拷贝--简洁版
class String
{
public:
String(const char* s = "")
{
if (NULL == s)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(s) + 1];
strcpy(_pStr, s);
}
}
String(String& s) :_pStr(NULL)
{
String temp(s._pStr);
swap(_pStr, temp._pStr);
}
String& operator=(String& s)
{
if (this != &s)
{
swap(_pStr, s._pStr);
}
return *this;
}
~String()
{
if (NULL != _pStr)
{
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
下面谈一下String类浅拷贝存在的问题
在浅拷贝时,两个对象指向了同一块内存,对象销毁时该空间被释放了两次,程序会崩溃!
下面给出引用计数
//引用计数
class String
{
public:
String(const char* s = "")
: _pCount(new int(1))
{
if (NULL == s)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(s) + 1];
strcpy(_pStr, s);
}
}
String(const String& s)
{
_pStr = s._pStr;
_pCount = s._pCount;
(*_pCount)++;
}
String& operator=(const String& s)
{
if (this != &s)
{
if (--(*_pCount) == 0)
{
delete[] _pStr;
delete _pCount;
}
_pStr = s._pStr;
_pCount = s._pCount;
(*_pCount)++;
}
return *this;
}
~String()
{
if (NULL != _pStr && --(*_pCount) == 0)
{
delete[] _pStr;
delete _pCount;
}
_pCount = NULL;
_pStr = NULL;
}
private:
char* _pStr;
int* _pCount;
};
用引用计数实现String类时引用计数使用静态成员变量的原因:
在引用计数引入计数虽然防止了内存被析构释放多次的可能,但是随之带来的却是计数器count的不统一,导致内存无法被释放。为了解决这个问题,所以加入了静态成员变量来保证计数器的统一,来方便后面内存空间的释放。
写时拷贝
//写时拷贝
class String
{
public:
String(const char *pstr = "")
{
if (NULL == pstr)
{
_pstr = new char[1 + 4];
*(_pstr + 4) = '\0';
}
else
{
_pstr = new char[strlen(pstr) + 1];
_pstr += 4;
strcpy(_pstr, pstr);
}
getRef() = 1;
}
String(const String &s)
:_pstr(s._pstr)
{
++getRef();
}
String& operator=(const String &s)
{
if (_pstr != s._pstr)
{
if (0 == --getRef())
{
delete[](_pstr - 4);
}
_pstr = s._pstr;
++getRef();
}
return *this;
}
char& operator[](size_t index)
{
return *(_pstr + index); //一空间,则一个改变,另一个也会改变
}
const char& operator[](size_t index)const
{
return *(_pstr + index);
}
char& operator[](size_t index)
{
if ((getRef()>1))
{
--getRef();
char* pstr = new char[strlen(_pstr) + 1 + 4];
strcpy(pstr, _pstr);
_pstr = pstr;
++getRef();
}
return *(_pstr + index);
}
~String()
{
if (0 == --getRef())
{
delete[](_pstr - 4);
_pstr = _pstr - 4;
_pstr = NULL;
}
}
private:
char *_pstr;
int& getRef()
{
return *((int*)_pstr - 1);
}
};