当前位置:   article > 正文

C++ STL --- string类模拟实现_cpp中string的模拟实现中的strlen

cpp中string的模拟实现中的strlen

目录

1.C语言必备函数

2.构造模块

  (1)构造函数

    [1]构造函数一

    [2]构造函数二

  (2)析构函数 

  (3)拷贝构造函数

    [1]传统版本

    [2]简洁版本

  (4)赋值运算符重载 

    [1]传统版本

    [2]简洁版本

3.容量模块

  (1)有效字符函数 

  (2)有效字符函数

  (3)扩容函数 

  (4)设置有效字符函数 

  (5)判空函数 

  (6)清空函数 

4.迭代器模块

  (1)正向迭代器

  (2)反向迭代器

5.访问模块

  (1)重载[]

  (2)重载cout<< 

6.修改模块

  (1)尾插单个字符

  (2)重载+=

7.特殊操作模块

  (1)转为C语言字符串类型

  (2)复制函数

  (3)正向查找函数

  (4)反向查找函数

8.总体实现


         在前面的文章中已经验证了,在涉及资源管理的类中,拷贝构造函数,析构函数,赋值运算符重载函数都需要用户显示实现,如果使用编译器默认生成的,就会有浅拷贝的风险。如果有读者不理解浅拷贝,可以看下面这幅图。

        浅拷贝通俗来讲就是多个对象指向了同一块空间,例如s1与s2同时指向此空间,当s1对象中的内容修改后,s2中的内容也随之被修改了。但这不是最严重的,最严重的是如果将s1对象销毁,这块空间将被释放,可是s2还在,如果它要对自己的成员变量做修改或是销毁s2,毫无疑问程序会崩溃,因为它此时已经是个野指针了,指向了一块被释放的空间。

        在string类的模拟实现中,主要有六大模块组成:构造模块、容量模块、迭代器模块、访问模块、修改模块、特殊操作模块。因为string类的操作太多,我们只实现其中最常用的功能即可。(本文代码均在win10系统下的vs2019下运行成功)

        在六个模块中的函数均为一部分代码,不能单独运行,总体中的代码才可以运行。   

        在准备模拟string类之前,需要先来回顾一下C语言中用来操作字符串的函数:strlen、strcpy、strncpy、strcat、memset、memcpy。这些函数在模拟string类的时候都要用到。     

1.C语言必备函数

函数名称函数功能
size_t strlen(const char *str)
计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
char *strcpy(char *dest, const char *src)

src 所指向的字符串复制到 dest

注意dest指向的空间要足够大。

char *strncpy(char *dest, const char *src, size_t n)
src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。
void *memset(void *str, int c, size_t n)
复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
void *memcpy(void *str1, const void *str2, size_t n)
从存储区 str2 复制 n 个字节到存储区 str1
strcat(char* s1,char* s2)src 所指向的字符串追加到 dest 所指向的字符串的结尾。

        在回顾函数之前,需要说明的是,在vs2019中这样定义一个字符串是会报错的:char* str = "abc";解决办法如下代码一:

        代码一:

  1. //代码一
  2. #include "iostream"
  3. using namespace std;
  4. #include <string.h>
  5. int main() {
  6. //方法1
  7. const char* s1 = "abc";
  8. cout << s1 << endl;
  9. //方法2
  10. char s2[] = { "1234" };
  11. char* p2 = s2;
  12. cout << p2 << endl;
  13. //方法3
  14. char* s3 = new char[100]{ "asd" };
  15. cout << s3 << endl;
  16. }

        解决上述问题后,开始回顾函数,六个函数的使用都在代码二中了: 使用这些函数时,请加上#define _CRT_SECURE_NO_WARNINGS,否则编译器会因为这些函数不安全而报错。

        代码二:

  1. //代码二
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include "iostream"
  4. using namespace std;
  5. #include <string.h>
  6. int main() {
  7. char* s1 = new char[100]{ "12345" };
  8. char* s2 = new char[100]{ "abcde" };
  9. char* s3 = new char[100];
  10. //strlen 计算s1有效元素个数
  11. int len = strlen(s1);
  12. cout << len << endl;//5
  13. //strcpy 把s2拷贝到s1中
  14. strcpy(s1, s2);
  15. cout << s1 << endl;//abcde
  16. //strcpy 把s1的前2个字符拷贝到s3中
  17. strncpy(s3, s1, 2);
  18. s3[2] = '\0';
  19. cout << s3 << endl;//ab
  20. //memset 把1个 '9'字符复制到s1的前1个位置
  21. memset(s1, '9', 1);
  22. cout << s1 << endl;//9bcde
  23. //memcpy 从s1中复制4个字节到s3中
  24. memcpy(s3, s1, 4);
  25. s3[4] = '\0';
  26. cout << s3 << endl;//9bcd
  27. //strcat 把s3指向的字符串追加到s1指向的字符串结尾
  28. strcat(s1, s3);
  29. cout << s1 << endl;//9bcde9bcd
  30. }

2.构造模块

  (1)构造函数

        这里实现两个构造函数。

        String类中的成员应该有一个字符串指针,一个有效元素个数变量,一个容量大小变量,一个npos变量(因为这是原本的string类中就有的)。为了方便起见,在设计阶段先将所有成员的访问权限均设置为public。同时将构造函数定义成全缺省函数。

    [1]构造函数一

        代码三:使用字符串构造对象

  1. //代码三
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include "iostream"
  4. using namespace std;
  5. class String {
  6. public:
  7. char* Str;//字符串指针
  8. size_t Size;//有效元素个数
  9. size_t Capacity;//容量
  10. static size_t npos;
  11. public:
  12. String(const char* s = "") {
  13. //将指向空的指针指向空字符串
  14. if (s == nullptr)
  15. s = "";
  16. //注意要比申请比字符串长度多1的空间来存储 '\0'
  17. Str = new char[strlen(s) + 1];
  18. //将s指向的空间中的内容拷贝到Str指向的空间中,这个操作会把'\0'一并拷贝
  19. strcpy(Str, s);
  20. //更新有效元素个数和容量
  21. Size = strlen(s);
  22. Capacity = strlen(s);
  23. }
  24. };
  25. size_t String::npos = -1;

    [2]构造函数二

        代码四:使用n个字符构造对象

  1. //代码四
  2. String(size_t n, char c) {
  3. //申请比有效元素多一个的空间
  4. Str = new char[n + 1];
  5. //把n个c存入Str指向的空间中
  6. memset(Str, c, n);
  7. //不要忘记设置'\0'
  8. Str[n] = '\0';
  9. //更新有效元素和容量
  10. //容量要保持比有效元素个数多
  11. //用来存储'\0'
  12. Size = n;
  13. Capacity = n+1;
  14. }

  (2)析构函数 

        代码五:析构函数一定要检查字符串指针是否指向空,如果是空就不可以再释放了。

  1. //代码五
  2. ~String() {
  3. //如果指向空值,释放程序会崩溃的
  4. if (Str)
  5. delete[] Str;
  6. Str = nullptr;
  7. Size = 0;
  8. Capacity = 0;
  9. }

  (3)拷贝构造函数

        这里提供两种版本的拷贝构造函数以供参考。

    [1]传统版本

        代码六:传统版本是先释放指向的旧空间,构造新空间,然后使用strcpy函数拷贝数据。

        注意这样有个缺点,虽然概率很小。万一申请空间失败呢?那样就连原有的数据都没有了。

  1. //代码六
  2. String(const String& s) {
  3. //需要释放原本的空间
  4. if (Str) {
  5. delete[] Str;
  6. }
  7. Str = new char[strlen(s.Str) + 1];//+1是为了存储'\0'
  8. strcpy(Str, s.Str);
  9. Size = strlen(Str);
  10. Capacity = Size+1;//+1是因为申请空间时就申请了这么多
  11. }

    [2]简洁版本

        代码七: 简洁版本是先使用目标字符串构造一个临时对象,然后交换两个对象的字符串指针。这样做不但不用担心空间申请失败,也不用自己手动释放空间。当临时对象生命周期结束,系统会自动调用析构函数。

  1. //代码七
  2. String(const String& s) {
  3. String temp(s.Str);
  4. //交换temp.Str和this->Str指向的空间
  5. std::swap(Str, temp.Str);
  6. Size = strlen(Str);
  7. Capacity = Size+1;
  8. }

  (4)赋值运算符重载 

        赋值运算符重载也提供了两个版本以供参考。

    [1]传统版本

        代码八:传统版本需要考虑是不是对象自己给自己赋值,若是就直接返回。一定要记得开辟空间时,要比字符串长度大,这是为了存储'\0'

  1. //代码八
  2. String& operator=(const String& s) {
  3. //判断是否是自己给自己赋值
  4. if (this != &s) {
  5. String temp(Str);
  6. //记得释放原本指向的空间
  7. delete[] Str;
  8. Str = new char[strlen(s.Str) + 1];
  9. strcpy(Str, s.Str);
  10. Size = strlen(s.Str);
  11. Capacity = Size+1;
  12. }
  13. return *this;
  14. }

    [2]简洁版本

        代码九:简介版本不需要考虑自己给自己赋值,反正都是交换空间。

  1. //代码九
  2. String& operator=(const String& s) {
  3. String temp(s.Str);
  4. //交换空间
  5. std::swap(Str, temp.Str);
  6. Size = strlen(s.Str);
  7. Capacity = Size +1;
  8. return *this;
  9. }

3.容量模块

  (1)有效字符函数 

        代码十:

  1. //代码十
  2. size_t SIZE() {
  3. return Size;
  4. }

  (2)有效字符函数

        代码十一: 

  1. //代码十一
  2. size_t CAPAcity() {
  3. return Capacity;
  4. }

  (3)扩容函数 

        代码十二:这里直接按照参数的两倍来扩容,记得需要释放原本的字符串空间。

  1. //代码十二
  2. void Reverse(size_t newCapacity) {
  3. //新容量大于旧容量
  4. if (newCapacity >= Capacity) {
  5. char* temp = new char[newCapacity *2];
  6. strcpy(temp, Str);
  7. delete[] Str;
  8. Str = temp;
  9. Capacity = 2 * newCapacity;
  10. }
  11. }

  (4)设置有效字符函数 

        代码十三:此函数机制为:新有效元素大于旧有效元素时可能需要扩容,继续判断,当新有效元素比容量还要大时需要扩容,扩容后使用memset函数添加元素。最后不要忘了手动加'\0'。

  1. //代码十三
  2. void Resize(size_t newSize, char c) {
  3. size_t oldSize = Size;
  4. size_t oldCapa = Capacity;
  5. //新的有效元素比旧的多
  6. if (newSize > oldSize) {
  7. //新元素比容量多 扩容
  8. if (newSize > oldCapa) {
  9. Reverse(newSize);
  10. }
  11. //往Str尾部插入合适数量的c
  12. memset(Str + Size, c, newSize - oldSize);
  13. }
  14. Size = newSize;
  15. Str[Size] = '\0';
  16. }

  (5)判空函数 

        代码十四:

  1. //代码十四
  2. bool Empty() {
  3. //容量等于0返回true
  4. return Size == 0;
  5. }

  (6)清空函数 

        代码十五:注意清空的含义是清空有效元素,不对容量进行操作。

  1. //代码十五
  2. void clear() {
  3. //只改变有效元素个数,不改变容量
  4. Size = 0;
  5. Str[0] = '\0';
  6. }

4.迭代器模块

        其实迭代器就是当作指针使用的,为了和string迭代器形式相似,我们要用这条语句给指针起个小名:typedef char* Iterator;

  (1)正向迭代器

        代码十六:

  1. //代码十六
  2. typedef char* Iterator;
  3. Iterator Begin() {
  4. return Str;
  5. }
  6. Iterator End() {
  7. return Str + Size;
  8. }

  (2)反向迭代器

        代码十七:反向迭代器可以直接复用正向迭代器。 

  1. //代码十七
  2. typedef char* Iterator;
  3. Iterator Rbegin() {
  4. return End();
  5. }
  6. Iterator Rend() {
  7. return Begin();
  8. }

5.访问模块

  (1)重载[]

        代码十八:注意一定要判断是否越界访问。同时也要多重载一份为const对象使用。使用assert来控制时要加头文件。#include<assert.h>

  1. //代码十八
  2. #include<assert.h>
  3. char& operator[](size_t pos) {
  4. assert(pos < Size);
  5. return Str[pos];
  6. }
  7. const char& operator[](size_t pos)const {
  8. assert(pos < Size);
  9. return Str[pos];
  10. }

  (2)重载cout<< 

        代码十九:注意<<不可以重载为成员函数,但我们还要使用私有变量,就把它设置为友元函数。至于为什么不可以重载为成员函数,我在《C++实现日期类》中已经总结了,感兴趣的可以去看看。

  1. //代码十九
  2. friend ostream& operator<<(ostream& _cout, const String& s) {
  3. _cout << s.Str;
  4. return _cout;
  5. }

6.修改模块

  (1)尾插单个字符

        代码二十:尾插时需要注意扩容条件。

  1. //代码二十
  2. void Push_back(char c) {
  3. //满足条件说明空间已经存满了,因为那个1是用来存储'\0'的
  4. if (Size + 1== Capacity) {
  5. Reverse(Capacity);
  6. }
  7. Str[Size] = c;
  8. Str[Size+1] = '\0';
  9. Size++;
  10. }

  (2)重载+=

        代码二十一:+=有两种,+=字符串、+=对象。后者可以复用前者。这里要特别注意如何判断是否要扩容,当两个字符串有效元素加起来再加上'\0'的大小不比容量小时就应该扩容。

  1. //代码二十一
  2. void operator+=(const char* s) {
  3. size_t oldSize = Size;
  4. //两个空间加起来元素个数
  5. size_t size = Size + strlen(s);
  6. //元素个数接近容量
  7. if (size+1 >= Capacity) {
  8. Reverse(size);
  9. }
  10. strcat(Str + oldSize, s);
  11. Size = size;
  12. }
  13. //直接复用上面的
  14. String& operator+=(const String& s) {
  15. *this += s.Str;
  16. return *this;
  17. }

7.特殊操作模块

  (1)转为C语言字符串类型

        代码二十二:

  1. //代码二十二
  2. const char* c_str()const
  3. {
  4. return Str;
  5. }

  (2)复制函数

        代码二十三:注意参数的调整问题。

  1. //代码二十三
  2. String Substr(size_t pos = 0, size_t n = npos) {
  3. //没有传参
  4. if (n == npos)
  5. n = Size;
  6. //传递的参数个数大于有效元素个数就需要进行调整
  7. if (pos + n >= Size) {
  8. n =Size - pos;
  9. }
  10. char* temp = new char[n + 1];
  11. strncpy(temp, Str + pos, n);
  12. temp[Size] = '\0';
  13. String Stemp(temp);
  14. delete[] temp;
  15. return Stemp;
  16. }

  (3)正向查找函数

        代码二十四:注意没找到就返回npos。npos是-1。

  1. //代码二十四
  2. size_t Find(char c, size_t pos = 0) {
  3. for (int i = pos; i < Size; i++) {
  4. if (Str[i] == c)
  5. return i;
  6. }
  7. return npos;
  8. }

  (4)反向查找函数

        代码二十五:

  1. //代码二十五
  2. size_t Rfind(char c, size_t pos = npos) {
  3. //查找的开始位置越界就将位置定在结尾
  4. pos = pos < Size ? pos : Size;
  5. for (int i = pos; i >= 0; i--) {
  6. if (Str[i] == c) {
  7. return i;
  8. }
  9. }
  10. return npos;
  11. }

8.总体实现

        以下是String类的模拟实现,在运行程序发现不足之处的,可以在评论区留言。

  1. #define _CRT_SECURE_NO_WARNINGS //解决C语言函数报错
  2. #include "iostream"
  3. #include<assert.h>
  4. using namespace std;
  5. class String {
  6. private:
  7. char* Str;//字符串指针
  8. size_t Size;//有效元素个数
  9. size_t Capacity;//容量
  10. static size_t npos;
  11. public:
  12. //构造模块
  13. /
  14. //构造函数1
  15. String(const char* s = "") {
  16. //将指向空的指针指向空字符串
  17. if (s == nullptr)
  18. s = "";
  19. //注意要比申请比字符串长度多1的空间来存储 '\0'
  20. Str = new char[strlen(s) + 1];
  21. //将s指向的空间中的内容拷贝到Str指向的空间中,这个操作会把'\0'一并拷贝
  22. strcpy(Str, s);
  23. //更新有效元素个数和容量
  24. Size = strlen(s);
  25. Capacity = strlen(s)+1;
  26. }
  27. //构造函数2
  28. String(size_t n, char c) {
  29. //申请比有效元素多一个的空间
  30. Str = new char[n + 1];
  31. //把n个c存入Str指向的空间中
  32. memset(Str, c, n);
  33. //不要忘记设置'\0'
  34. Str[n] = '\0';
  35. //更新有效元素和容量
  36. //容量要保持比有效元素个数多
  37. //用来存储'\0'
  38. Size = n;
  39. Capacity = n+1;
  40. }
  41. //析构函数
  42. ~String() {
  43. //如果指向空值,释放程序会崩溃的
  44. if (Str)
  45. delete[] Str;
  46. Str = nullptr;
  47. Size = 0;
  48. Capacity = 0;
  49. }
  50. //传统版拷贝构造
  51. /*
  52. String(const String& s) {
  53. //需要释放原本的空间
  54. if (Str) {
  55. delete[] Str;
  56. }
  57. Str = new char[strlen(s.Str) + 1];
  58. strcpy(Str, s.Str);
  59. Size = strlen(Str);
  60. Capacity = Size+1;
  61. }
  62. */
  63. //简洁版拷贝构造
  64. String(const String& s) {
  65. String temp(s.Str);
  66. //交换temp.Str和this->Str指向的空间
  67. std::swap(Str, temp.Str);
  68. Size = strlen(Str);
  69. Capacity = Size+1;
  70. }
  71. //传统版赋值运算符重载
  72. /*
  73. String& operator=(const String& s) {
  74. //判断是否是自己给自己赋值
  75. if (this != &s) {
  76. String temp(Str);
  77. //记得释放原本指向的空间
  78. delete[] Str;
  79. Str = new char[strlen(s.Str) + 1];
  80. strcpy(Str, s.Str);
  81. Size = strlen(s.Str);
  82. Capacity = Size + 1;
  83. }
  84. return *this;
  85. }
  86. */
  87. //简洁版赋值运算符重载
  88. String& operator=(const String& s) {
  89. String temp(s.Str);
  90. //交换空间
  91. std::swap(Str, temp.Str);
  92. Size = strlen(s.Str);
  93. Capacity = Size +1;
  94. return *this;
  95. }
  96. //容量模块
  97. /
  98. //有效元素
  99. size_t SIZE() {
  100. return Size;
  101. }
  102. //容量
  103. size_t CAPAcity() {
  104. return Capacity;
  105. }
  106. //扩容函数
  107. void Reverse(size_t newCapacity) {
  108. //新容量大于旧容量
  109. if (newCapacity >= Capacity) {
  110. char* temp = new char[newCapacity *2];
  111. strcpy(temp, Str);
  112. delete[] Str;
  113. Str = temp;
  114. Capacity = 2 * newCapacity;
  115. }
  116. }
  117. //设置有效元素
  118. void Resize(size_t newSize, char c) {
  119. size_t oldSize = Size;
  120. size_t oldCapa = Capacity;
  121. //新的有效元素比旧的多
  122. if (newSize > oldSize) {
  123. //新元素比容量多 扩容
  124. if (newSize > oldCapa) {
  125. Reverse(newSize);
  126. }
  127. //往Str尾部插入合适数量的c
  128. memset(Str + Size, c, newSize - oldSize);
  129. }
  130. Size = newSize;
  131. Str[Size] = '\0';
  132. }
  133. //判空函数
  134. bool Empty() {
  135. //容量等于0返回true
  136. return Size == 0;
  137. }
  138. //清空函数
  139. void clear() {
  140. //只改变有效元素个数,不改变容量
  141. Size = 0;
  142. Str[0] = '\0';
  143. }
  144. //迭代器模块
  145. /
  146. typedef char* Iterator;
  147. //获取正向首指针
  148. Iterator Begin() {
  149. return Str;
  150. }
  151. //获取正向尾指针
  152. Iterator End() {
  153. return Str + Size;
  154. }
  155. typedef char* Iterator;
  156. //获取反向首指针
  157. Iterator Rbegin() {
  158. return End();
  159. }
  160. //获取反向尾指针
  161. Iterator Rend() {
  162. return Begin();
  163. }
  164. //访问模块
  165. /
  166. //普通[]重载
  167. char& operator[](size_t pos) {
  168. assert(pos < Size);
  169. return Str[pos];
  170. }
  171. //const[]重载
  172. const char& operator[](size_t pos)const {
  173. assert(pos < Size);
  174. return Str[pos];
  175. }
  176. // <<重载
  177. friend ostream& operator<<(ostream& _cout, const String& s) {
  178. _cout << s.Str;
  179. return _cout;
  180. }
  181. //访问模块
  182. /
  183. //尾插
  184. void Push_back(char c) {
  185. //满足条件说明空间已经存满了,因为那个1是用来存储'\0'的
  186. if (Size + 1== Capacity) {
  187. Reverse(Capacity);
  188. }
  189. Str[Size] = c;
  190. Str[Size+1] = '\0';
  191. Size++;
  192. }
  193. //+=重载
  194. void operator+=(const char* s) {
  195. size_t oldSize = Size;
  196. //两个空间加起来元素个数
  197. size_t size = Size + strlen(s);
  198. //元素个数接近容量
  199. if (size+1 >= Capacity) {
  200. Reverse(size);
  201. }
  202. strcat(Str + oldSize, s);
  203. Size = size;
  204. }
  205. //+=重载
  206. String& operator+=(const String& s) {
  207. *this += s.Str;
  208. return *this;
  209. }
  210. //特殊操作模块
  211. /
  212. //转换C语言字符串
  213. const char* c_str()const
  214. {
  215. return Str;
  216. }
  217. //复制
  218. String Substr(size_t pos = 0, size_t n = npos) {
  219. if (n == npos)
  220. n = Size;
  221. if (pos + n >= Size) {
  222. n =Size - pos;
  223. }
  224. char* temp = new char[n + 1];
  225. strncpy(temp, Str + pos, n);
  226. temp[Size] = '\0';
  227. String Stemp(temp);
  228. delete[] temp;
  229. return Stemp;
  230. }
  231. //正向查找
  232. size_t Find(char c, size_t pos = 0) {
  233. for (int i = pos; i < Size; i++) {
  234. if (Str[i] == c)
  235. return i;
  236. }
  237. return npos;
  238. }
  239. //反向查找
  240. size_t Rfind(char c, size_t pos = npos) {
  241. pos = pos < Size ? pos : Size;
  242. for (int i = pos; i >= 0; i--) {
  243. if (Str[i] == c) {
  244. return i;
  245. }
  246. }
  247. return npos;
  248. }
  249. };
  250. size_t String::npos = -1;
  251. int main() {
  252. const char* sp = "abc12345566743212343455655";
  253. String s1(sp);
  254. String s2 = s1;
  255. auto it = s2.Begin();
  256. while (it != s2.End()) {
  257. cout << *it << " ";
  258. it++;
  259. }
  260. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/388343
推荐阅读
相关标签
  

闽ICP备14008679号