irpas技术客

C++:使用类模拟 string | 柔性数组 | 运算符重载 | 写实拷贝_Sauron7i

网络投稿 1843

前言

如果在模拟string类时提出这种问题:若多个对象指向同一个字符串,那么会占用很多空间,如何解决? 解决方法之一就是设计一个包含柔性数组和引用计数的结构体。

目录 前言柔性数组结构体设计类的设计私有成员构造函数析构函数拷贝构造函数赋值运算符重载重载[]运算符1写实拷贝 重载[]运算符2修改某个元素 modify移动构造与移动赋值函数 重载(+)运算符重载(+=)运算符 end源代码

柔性数组结构体设计

设计一个结构体来模拟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(p) {} 重载(+=)运算符

考虑这些情况: 当两个对象都不为空时:

如果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; }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #C使用类模拟 #string #柔性数组 #运算符重载 #写实拷贝