2)复制构造函数
String::String(const String &s):
_cstr(s._cstr),
_used(s._used),
_length(s._length),
_capacity(s._capacity)
{
++*_used;
} 本函数非常易懂,就是把s的成员的值全部复制给*this即可,但是由于多了*this这个对象引用s的字符数组,所以应该把该字符数组的引用计数加1。注意,此时this->_used和s._used指向了同一个对象。
3)带C字串参数的构造函数
String::String(const char *cstr, size_t len)
{
if(cstr == NULL)
return;
size_t str_len = strlen(cstr);
if(len <= str_len)
{
_initString(cstr, len);
}
}
void String::_initString(const char *cstr, size_t len)
{
if(cstr == NULL)
return;
_cstr = new char[len + 1];
memcpy(_cstr, cstr, len);
_cstr[len] = 0;
_used = new size_t(1);
_length = len;
_capacity = len;
} 该函数非常简单,由于是构造函数,而且使用的参数是C风格的字符串,所以默认为其字符串一定不是某个对象所引用的字符数组,所以直接为其分配内存,并复制字符。非常明显,因为是该对象第一个创建该字符数组的,所以其引用为1.
4)带C风格字符串的构造函数
String::String(const char *cstr)
{
if(cstr == NULL)
return;
size_t len = strlen(cstr);
_initString(cstr, len);
} 其实现与上原理相同,只是参数不同,不再详述。
5)析构函数
String::~String()
{
_decUsed();
}
void String::_decUsed()
{
--*_used;
if(*_used == 0)
{
if(_cstr != NULL)
{
delete[] _cstr;
_cstr = NULL;
_length = 0;
_capacity = 0;
}
delete _used;
_used = NULL;
}
} _decUsed()函数可以说是该类内存释放的管理函数,可以看到,每当一个对象被析构时,其指向的堆中的字符数组的引用计数就会减1,当引用计数为0时,就释放字符数组和引用计数。
String& String::operator=(const String &s)
{
++*(s._used);
_decUsed();
_cstr = s._cstr;
_length = s._length;
_capacity = s._capacity;
return *this;
} 该赋值操作函数的参数一个本类的对象,该类赋值操作函数第一个要避免的就是自身赋值的问题,在有指针存在的类中是特别要重视这个问题,而在这个String类也不可例外。为什么这样说呢?因为我们调用赋值操作函数时,必须要减少左值的引用计数,增加右值的引用计数,这个在第1)点已经说过了,而如果是自身赋值的话,在减少其引用计数时,其引用计数可能为0,从而导致字符数组的释放,从而让_cstr指针悬空(delete[]掉了,却在赋值的过程中,重新赋为delete[]前的值,即_cstr的值没有在赋值过程中改变)。
一般的程序的做法是判断参数的地址与this是否相等来避免自身赋值,而这里却可以采用巧妙的策略来避免这个问题,可以看到上面的代码并没有if判断语句。我们首先对*s._used加1,这样*s._used至少为2,然后再对*(this->_used)减1,这样即使s与*this是同一个对象,也可以保证*(this->_used)的值至少为1,不会变为0,从而让字符数组不会被释放。因为复制是使用隐式共享的,所以直接复制指针,使指针_cstr其指向与s同一个存在中的字符数组并复制其他的数据成员即可。
同时,我们还要记得返回当前对象的引用。
7)重载的赋值操作函数
String& String::operator=(const char *cstr)
{
if(cstr != NULL)
{
_decUsed();
size_t len = strlen(cstr);
_initString(cstr, len);
}
return *this;
} 该赋值操作函数的参数一个C风格的字符串,因而不会发生自身赋值的问题。与String(const char *cstr)函数相似,唯一不同的是使用赋值操作函数时,对象已经存在,所以要调用_decUsed来减少该对象的_cstr原先指向的字符数组的引用计数。然后生成根据cstr创建一个全新的字符数组。并返回当前对象的引用。
8)重载+=操作符,实现字符串连接
String& String::operator+=(const String &s)
{
_addAssignOpt(s._cstr, s._length);
return *this;
}
String& String::operator+=(const char *cstr)
{
if(cstr != NULL)
_addAssignOpt(cstr, strlen(cstr));
return *this;
}
void String::_addAssignOpt(const char *cstr, size_t len)
{
if(*_used == 1)
_addString(cstr, len);
else
{
_decUsed();
_cstr = _renewAndCat(cstr, len);
_used = new size_t(1);
}
}
void String::_addString(const char *cstr, size_t len)
{
//本函数,只有在引用计数为1时,才可用
if(*_used != 1)
return;
if(len + _length > _capacity)
{
char *ptr = _renewAndCat(cstr, len);
delete[] _cstr;
_cstr = ptr;
}
else
{
strncat(_cstr, cstr, len);
_length += len;
}
}
char* String::_renewAndCat(const char *cstr, size_t len)
{
size_t new_len = len + _length;
size_t capacity = new_len;
capacity += (capacity >> 1);
char *ptr = new char[capacity+1];
if(_cstr != NULL)
memcpy(ptr, _cstr, _length);
ptr[_length] = 0;
_length = new_len;
_capacity = ca