前言
如果在模拟string类时提出这种问题:若多个对象指向同一个字符串,那么会占用很多空间,如何解决?
解决方法之一就是设计一个包含柔性数组和引用计数的结构体。
目录
柔性数组结构体设计
设计一个结构体来模拟string
struct StrNode
{
int ref; //引用计数
int len; //字符串长度
int size; //空间大小
char data[]; //柔性数组
};
类的设计
私有成员
-
因为要设计类,所以将柔性数组结构体(StrNode)也设计在类中,保证封装特性。
-
定义一个StrNode的指针作为私有成员,旨在构造对象时使用。
class String
{
private:
struct StrNode
{
int ref; //引用计数
int len; //字符串长度
int size; //空间大小
char data[]; //柔性数组
};StrNode* pstr;
}
构造函数
String(const char* p = NULL) : pstr(NULL)
{
if (p != NULL)
{
int len = strlen(p);
pstr = (StrNode*)malloc(sizeof(StrNode) + len * 2 + 1);
pstr->ref = 1;
pstr->len = len;
pstr->size = len * 2;
strcpy(pstr->data, p);
}
}
构造对象示例:
int main(void)
{
String s1("hello");
return 0;
}
内存简图:
析构函数
~String()
{
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
pstr = NULL;
}
- 首先要判断pstr是否为NULL,不为NULL时再对其引用计数 ref -= 1
- 若减完 ref为0,说明再没有对象指向该字符串,需要将其空间释放。
- 释放完成后将pstr 置为NULL。
拷贝构造函数
由于设计时有引用计数这个概念,所以拷贝构造时候,在判断源对象pstr不为NULL前提下,只需将
-
pstr指针指向同一空间后
-
引用计数加一即可
String(const String& s) : pstr(NULL) { if (s.pstr != NULL) { pstr = s.pstr; pstr->ref += 1; } }
示例:
int main(void)
{
String s1("hello");
String s2(s1);
String s3(s2);
return 0;
}
运行示例:
赋值运算符重载
赋值运算符流程
-
待赋值对象是否和源对象是同一个对象,若不是则
-
如果待赋值对象不为空且只有他一个对象指向堆空间,则释放该空间,若对象本身为空,不执行
-
将源对象赋值给待赋值对象
-
如果源对象不为空,那么赋值过后的待赋值对象也指向堆空间,则需要将引用计数加一
-
返回this指针
// 赋值构造 String& operator =(const String& s) { if (this != &s) { if (pstr != NULL && --pstr->ref == 0) { free(pstr); } pstr = s.pstr; if (pstr != NULL) { pstr->ref += 1; } } return *this; }
示例:
int main(void)
{
String s1("hello");
String s2(s1);
String s3("world");
s3 = s2;
return 0;
}
在没有赋值前,对象结构为:
赋值后,s3也指向该堆空间,将引用计数加一
重载[]运算符1
我们希望通过 []运算符来改变对象的字符串的某个元素,但如果是这种情况:
int main(void)
{
String s1("hello");
String s2(s1);
s1[1] = 'a';
cout << s1 << endl;
return 0;
}
s1和s2指向同一个字符串,那么如果改变 s1,必然会改变 s2的,为了解决这种问题,需要利用写实拷贝。
写实拷贝
考虑条件:
-
边界条件,如果为空,程序退出。如果下标不在范围内,程序终止。
-
判断引用计数是否大于1,如果不大于,那么可以直接返回该下标元素,如果大于1,说明还有其他对象也指向该字符串,需要拷贝。
-
重新申请堆区空间,将源空间内容拷贝进去
-
将新空间的引用计数加一,源空间引用计数减一,最后将新空间赋给该对象的指针pstr。
char& operator [](const int index) { if (pstr == NULL) exit(1); assert(index >= 0 && index <= pstr->len - 1); if (pstr->ref > 1) { int total = sizeof(StrNode) + pstr->size + 1; StrNode* newNode = (StrNode*)malloc(total); memmove(newNode, pstr, total); newNode->ref = 1; pstr->ref -= 1; pstr = newNode; } return pstr->data[index]; }
示例:
重载[]运算符2
如果不希望这个运算符是双向的作用:即即可利用它取值,又可对其赋值,那么就不需要返回一个引用,也不用写实拷贝。
char operator [](const int index)const
{
assert(index >= 0 && index <= pstr->size - 1);
return pstr->data[index];
}
修改某个元素 modify
当我们考虑完需求,发现既然想通过[]运算符来修改某个值,那写实拷贝也没有存在的必要,直接写一个修改函数即可。代码和写实拷贝相同。
bool modify(const int index, const char ch) {
assert(pstr != NULL && index >= 0 && index <= pstr->size - 1);
if (pstr->ref > 1) {
int total = sizeof(StrNode) + pstr->size + 1;
StrNode* newNode = (StrNode*)malloc(total);
memmove(newNode, pstr, total);
newNode->ref = 1;
pstr->ref -= 1;
pstr = newNode;
}
pstr->data[index] = ch;
return true;
}
移动构造与移动赋值函数
-
移动构造,就是将自己的资源进行转移,转移到要构造的对象里。
-
移动赋值,将自己的资源赋值给目标对象,自身置为NULL。
// 移动构造
String(String&& s) : pstr(NULL)
{
pstr = s.pstr;
s.pstr = NULL;
}// 移动赋值
String& operator =(String&& s)
{
if (this == &s) return *this;
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
pstr = s.pstr;
s.pstr = NULL;
return *this;
}
示例:
String fun()
{
String s2("hello");
return s2;
}
int main(void)
{
String s1;
s1 = fun();
return 0;
}
在fun()函数对 s2构造时:
在执行到返回s2这条语句时,将移动构造一个将亡值,再将 s2本身置为NULL。
在返回主函数执行移动赋值,用刚才的将亡值赋值给s1,图示中的this指针就是 s1,对 s1进行了移动赋值。
重载(+)运算符
要完成这个函数,需要考虑这些情况:
-
如果两个对象都为空,则直接返回空(String())。
-
如果this指针不为空,被调用对象为空,则返回this指针
-
如果this指针为空,被调用对象不为空,返回该对象。
-
如果上述情况都不满足,则进行编写。
-
在最后返回时需要调用构造函数,为了防止调用最初所写的构造,所以重载一个特殊的构造函数。
// operator+ String operator+(const String& s)const { if (pstr == NULL && s.pstr == NULL) { return String(); } else if (pstr != NULL && s.pstr == NULL) { return *this; } else if (pstr == NULL && s.pstr != NULL) { return s; } else { int total = (pstr->len + s.pstr->len) * 2; StrNode* newNode = (StrNode*)malloc(total + 1); strcpy(newNode->data, pstr->data); strcat(newNode->data, s.pstr->data); newNode->ref = 1; newNode->len = pstr->len + s.pstr->len; newNode->size = total; return String(newNode); } }
// 特殊的构造函数
private:
String(StrNode* p) : pstr§ {}
重载(+=)运算符
考虑这些情况:
当两个对象都不为空时:
-
如果this指针所指对象的引用计数大于1,则开辟新空间
-
如果引用计数等于1,那么如果size小于两个字符串长度之和,就对其扩容,否则直接拼接。
-
如果this指针对象为空,另一个对象不为空,则直接赋值给this指针所指对象,引用计数加一。
-
不满足上述条件,那么什么都不做,直接返回this
// operator+=() String& operator +=(const String& s) { if (pstr != NULL && s.pstr != NULL) { if (pstr->ref > 1) { int total = pstr->len + s.pstr->len; pstr->ref -= 1; char* tmp = pstr->data; // pstr = (StrNode*)malloc(sizeof(StrNode) + total * 2 + 1); strcpy(pstr->data, tmp); strcat(pstr->data, s.pstr->data); pstr->ref = 1; pstr->len = total; pstr->size = total * 2; } else { int total = pstr->len + s.pstr->len; if (pstr->size < total) { pstr = (StrNode*)realloc(pstr, sizeof(StrNode) + total * 2 + 1); pstr->size = total * 2; } strcat(pstr->data, s.pstr->data); pstr->len = total; } } else if (this->pstr == NULL && s.pstr != NULL) { pstr = s.pstr; pstr->ref += 1; } return *this; }
示例:
int main(void)
{
String s1("Hello");
String s2("world");
s1 += s2;
return 0;
}
第一种情况,两个数据相加没超过源对象的size ,直接拼接即可
第二种情况,加等时数据超过size:
int main(void)
{
String s1("Hello");
String s2("world2022_3_12");
s1 += s2;
return 0;
}
执行完毕后, s1的size扩容到 38了。
end源代码
class String
{
private:
struct StrNode
{
int ref; //引用计数
int len; //字符串长度
int size; //空间大小
char data[]; //柔性数组
};
StrNode* pstr;
String(StrNode* p) : pstr(p) {}
public:
String(const char* p = NULL) : pstr(NULL)
{
if (p != NULL)
{
int len = strlen(p);
pstr = (StrNode*)malloc(sizeof(StrNode) + len * 2 + 1);
pstr->ref = 1;
pstr->len = len;
pstr->size = len * 2;
strcpy(pstr->data, p);
}
}
~String()
{
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
pstr = NULL;
}
// 拷贝构造
String(const String& s) : pstr(NULL)
{
if (s.pstr != NULL)
{
pstr = s.pstr;
pstr->ref += 1;
}
}
// 赋值构造
String& operator =(const String& s)
{
if (this != &s)
{
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
pstr = s.pstr;
if (pstr != NULL)
{
pstr->ref += 1;
}
}
return *this;
}
//写实拷贝
char& operator [](const int index)
{
if (pstr == NULL) exit(1);
assert(index >= 0 && index <= pstr->len - 1);
if (pstr->ref > 1)
{
int total = sizeof(StrNode) + pstr->size + 1;
StrNode* newNode = (StrNode*)malloc(total);
memmove(newNode, pstr, total);
newNode->ref = 1;
pstr->ref -= 1;
pstr = newNode;
}
return pstr->data[index];
}
char operator [](const int index)const
{
assert(pstr != NULL && index >= 0 && index <= pstr->size - 1);
return pstr->data[index];
}
// 修改
bool modify(const int index, const char ch)
{
assert(pstr != NULL && index >= 0 && index <= pstr->size - 1);
if (pstr->ref > 1)
{
int total = sizeof(StrNode) + pstr->size + 1;
StrNode* newNode = (StrNode*)malloc(total);
memmove(newNode, pstr, total);
newNode->ref = 1;
pstr->ref -= 1;
pstr = newNode;
}
pstr->data[index] = ch;
return true;
}
// 移动构造
String(String&& s) : pstr(NULL)
{
pstr = s.pstr;
s.pstr = NULL;
}
// 移动赋值
String& operator =(String&& s)
{
if (this == &s) return *this;
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
pstr = s.pstr;
s.pstr = NULL;
return *this;
}
// operator+
String operator+(const String& s)const
{
if (pstr == NULL && s.pstr == NULL)
{
return String();
}
else if (pstr != NULL && s.pstr == NULL)
{
return *this;
}
else if (pstr == NULL && s.pstr != NULL)
{
return s;
}
else
{
int total = (pstr->len + s.pstr->len) * 2;
StrNode* newNode = (StrNode*)malloc(total + 1);
strcpy(newNode->data, pstr->data);
strcat(newNode->data, s.pstr->data);
newNode->ref = 1;
newNode->len = pstr->len + s.pstr->len;
newNode->size = total;
return String(newNode);
}
}
// operator+=()
String& operator +=(const String& s)
{
if (pstr != NULL && s.pstr != NULL)
{
if (pstr->ref > 1)
{
int total = pstr->len + s.pstr->len;
pstr->ref -= 1;
char* tmp = pstr->data; //
pstr = (StrNode*)malloc(sizeof(StrNode) + total * 2 + 1);
strcpy(pstr->data, tmp);
strcat(pstr->data, s.pstr->data);
pstr->ref = 1;
pstr->len = total;
pstr->size = total * 2;
}
else
{
int total = pstr->len + s.pstr->len;
if (pstr->size < total)
{
pstr = (StrNode*)realloc(pstr, sizeof(StrNode) + total * 2 + 1);
pstr->size = total * 2;
}
strcat(pstr->data, s.pstr->data);
pstr->len = total;
}
}
else if (this->pstr == NULL && s.pstr != NULL)
{
pstr = s.pstr;
pstr->ref += 1;
}
return *this;
}
// 输出运算符
ostream& operator <<(ostream& out) const
{
if (pstr != NULL)
{
out << pstr->data;
}
return out;
}
};
ostream& operator<<(ostream& out, const String& s)
{
s << out;
return out;
}