1. string类型字符串存储方式的简单说明
string实现方式:比较典型的有eager-copy(贪婪拷贝);copy-on-write(写时复制);small string optimization(短字符串优化)方式。
string str1 = "I love China"
string str2 = str1;
printf("str1存储的地址:%p".str1.c_str())
printf("str2存储的地址:%p".str2.c_str())
可以看出采用eager-copy方式实现。浪费内存了。
利用写时复制的方式实现自己的string类。
2. 通过copy-on-write方式实现string类型
1. 框架与技术设计
一般的string类设计
class mystring
{
public:
mystring(const char* tmpstr = "") //
{
}
mystring(const mystring& tmpstr) {
point = new char[strlen(tmpstr.point) + 1];
strcpy(point, tmpstr.point);
}
mystring& operator= (const mystring& tmpstr) {
if (this == &tmpstr) {
return *this;
}
delete[] point;
// 如果要这么写,下图中,kxstr3和kxstr1就不是指向同一块内存了
point = new char[strlen(tmpstr.point) + 1]; // 给字符串末尾\0留位置
strcpy(point, tmpstr.point);
return *this;
}
private:
char* point; // 指向实际的字符串
};
mystring kxstr1("I love China");
mystring kxstr2 = ("I love China");
mystring kxstr3 = kxstr1; // 调用拷贝构造函数
kxstr2 = kxstr1; // 调用赋值拷贝运算符
改造后的string类设计
如果采用以上的拷贝构造函数,那么无法满足图中的共享内存的方式。接下来我们重新写一个类stringvalue,也就是把要保存的字符串以及对字符串的引用计数统一的保存在一个对象中。
class mystring
{
public:
mystring(const char* tmpstr = ""): pvalue(new stringvalue(tmpstr)) //构造函数中就把stringvalue构造出来!!!
{
}
// 1. pvalue都指向堆中的stringvalue对象
// 2. 引用计数+1
mystring(const mystring& tmpstr):pvalue(tmpptr.pvalue) { // 拷贝构造函数
++pvalue->refcount;
}
mystring& operator= (const mystring& tmpstr) {
if (this == &tmpstr) {
return *this;
}
--pvalue->refcount; // 本身存在对象(*this),自己所指向的字符串引用计数先减一
if(pvalue->refcount == 0)
delete pvalue; // 如果原来指向的堆中的引用计数为0了,那么删除原来的指针
pvalue = tmpstr.pvalue;
++pvalue -> refcount;
}
~mystring()
{
--pvalue->refcount;
if (pvalue->refcount==0)
{
delete pvalue;
}
}
private:
//char* point; // 指向实际的字符串
// 类中类
struct stringvalue
{
size_t refcount; // 引用计数
char* point; // 指向实际字符串的指针
stringvalue(const char* tmpstr):refcount(1) // 构造函数
{
point = new char[strlen(tmpstr) + 1];
strcpy(point, tmpstr.point);
}
~stringvalue()
{
delete[] point;
}
}
};
- 创建两个不同的mystring对象时,及时字符串相同,也不会共享内存。他们的关系如图所示:
mystring kxstr1("I love China");
mystring kxstr2("I love China");
mystring kxstr3 = kxstr1; // 调用mystring类的拷贝构造函数
kxstr2 = kxstr1; // 调用mystring类的赋值拷贝运算符
记住一个原则,如果一个类中包含有指针成员(比如mystring类中的pvalue),那么一般需要手工为该类书写拷贝构造函数和赋值拷贝运算符。