当前位置:   article > 正文

C++:String的模拟实现

C++:String的模拟实现

      

     模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程

C++:String类的使用-CSDN博客

      String模拟实现大致框架迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义)  并且为了不和库的string冲突,我们需要自己搞一个命名空间

  1. namespace cyx
  2. {
  3. class string
  4. {
  5. public:
  6. //迭代器的实现(可读可写)
  7. typedef char* iterator;
  8. iterator begin()
  9. {
  10. return _str;
  11. }
  12. iterator end()
  13. {
  14. return _str + _size;
  15. }
  16. //迭代器的实现(可读不可写)
  17. typedef const char* const_iterator;
  18. const_iterator begin() const
  19. {
  20. return _str;
  21. }
  22. const_iterator end() const
  23. {
  24. return _str + _size;
  25. }
  26. private:
  27. char* _str;
  28. size_t _size;
  29. size_t _capacity;
  30. static const size_t npos;
  31. //static const size_t npos=-1 vs下int类型支持给static和const同时修饰的变量用缺省值
  32. };
  33. const size_t string::npos = -1;//静态成员遍历类外初始化
  34. }

      nops是一个静态变量,要类内定义类外初始化,由于nops是size_t类型,赋值-1会被强转成最大的无符号整数

一、构造+析构+赋值重载(Member functions)

1.1 全缺省构造函数

  1. //构造函数
  2. string(const char* str = "")
  3. :_size(strlen(str))
  4. {
  5. _capacity = _size == 0 ? 3 : _size;
  6. _str = new char[_capacity + 1];// 多开一块\0的空间
  7. strcpy(_str, str);
  8. }

1、 “ ”空字符串其实里面默认就有\0,所以缺省值直接给空字符串就行

2、_capacity 一定要给初始空间,不然后面如果涉及到2倍扩容,为0的话就扩不了了

3、要多开一块空间,连这个\0 

4、可以复用strcpy函数

1.2 拷贝构造函数的传统写法

     传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝

  1. string(const string& s)
  2. :_size(s._size)
  3. , _capacity(s._capacity)
  4. {
  5. _str = new char[_capacity + 1];
  6. strcpy(_str, s._str);
  7. }

1.3 拷贝构造函数的现代写法和swap函数

       现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)

  1. //交换字符串
  2. void swap(string& s)
  3. {
  4. std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向
  5. std::swap(_size, s._size);
  6. std::swap(_capacity, s._capacity);
  7. }
  1. string(const string& s)
  2. :_str(nullptr)
  3. {
  4. string temp(s._str);
  5. swap(temp);
  6. }

 传统写法和现代写法参数一样,不能重载,只能保留一个 

1. 4 迭代器区间构造

  1. //迭代器区间构造
  2. template <class InputIterator>
  3. string(InputIterator first, InputIterator last)
  4. :_str(new char[last-first+1])
  5. ,_size(0)
  6. ,_capacity(last-first)
  7. {
  8. while (first != last)
  9. {
  10. push_back (*first);
  11. ++first;
  12. }
  13. }

       这里定义的模版InputIterator的意思其实是这边我们可以传不同类型对象的迭代器,我们并不知道这个迭代器里面有多少元素,所以得用指针-指针,即last-first来确定我们的容量,然后再开空间,一个个进行尾插。

1.5 赋值重载的传统写法

       传统的思路就是,先开一块新空间拷贝旧数据,然后再释放掉原空间,这里尽量是先开空间再释放,避免我们开空间失败导致原始数据的丢失。   

  1. //赋值重载(传统写法)
  2. string& operator=(const string& s)
  3. {
  4. if (this != &s)//避免自赋值
  5. {
  6. //先开新空间再毁旧空间,避免新空间开失败导致数据丢失
  7. char* temp = new char[s._capacity + 1];
  8. strcpy(temp, s._str);
  9. delete[]_str;
  10. _str = temp;
  11. _size = s._size;
  12. _capacity = s._capacity;
  13. }
  14. return *this;
  15. }

注意:要注意自赋值情况!!否则刚拷贝完自己就被释放了

1.6 赋值重载的现代写法

      现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。

  1. //赋值重载(现代写法)
  2. string& operator=(string s)//必须用值传递,否则会导致原数据的丢失
  3. {
  4. swap(s);
  5. return *this;
  6. }

  传统写法和现代写法参数不一样,一个是const引用,一个传值传参,所以可以同时存在。

1.7 析构函数

  1. ~string()
  2. {
  3. delete[]_str;
  4. _str = nullptr;
  5. _size = _capacity = 0;
  6. }

 二、容量相关的接口(Capacity)

2.1 size、capacity

  1. //获取当前size
  2. size_t size() const
  3. {
  4. return _size;
  5. }
  6. //获取当前capacity
  7. size_t capacity() const
  8. {
  9. return _capacity;
  10. }

2.2 reserve

如果n比_capacity大,思路就是先开新空间进行拷贝,然后再释放旧空间

  1. //改变capacity
  2. void reserve(size_t n)
  3. {
  4. if (n > _capacity)
  5. {
  6. char* tmp = new char[n + 1];
  7. strcpy(tmp, _str);
  8. delete[] _str;
  9. _str = tmp;
  10. _capacity = n;
  11. }
  12. }

2.3 resize

如果n比_size大,我们根据n去扩容,然后因为string的底层是字符数组,所以memset就很适合,他就是可以去一个字节一个字节设置成我们想要的。缺省值给‘\0’

  1. //改变size
  2. void resize(size_t n, char ch = '\0')
  3. {
  4. if (n > _size)
  5. {
  6. reserve(n);
  7. memset(_str + _size, ch, n - _size);
  8. }
  9. _size = n;
  10. _str[_size] = '\0';
  11. }

2.4 clear和empty

  1. //清理字符串
  2. void clear()
  3. {
  4. _str[0] = '\0';
  5. _size = 0;
  6. }
  7. //判断字符串是否为空
  8. bool empty()
  9. {
  10. return _capacity == 0;
  11. }

2.5 shrink_to_fit(用得少)

缩容到size位置,平时用的很少,我们要尽量减少扩容,思路也是一样的,开辟新空间去拷贝,再释放旧空间

  1. void shrink_to_fit()
  2. {
  3. char* temp = new char[_size + 1];
  4. strcpy(temp, _str);
  5. delete[] _str;
  6. _str = temp;
  7. _capacity = _size;
  8. }

三、[ ]和比较运算符重载

  1. //[]重载(可读可写)
  2. char& operator[](size_t pos)
  3. {
  4. assert(pos < _size);//确保地址有效
  5. return _str[pos];
  6. }
  7. //[]重载(可读不可写)
  8. const char& operator[](size_t pos) const
  9. {
  10. assert(pos < _size);//确保地址有效
  11. return _str[pos];
  12. }
  13. //比较类型重载
  14. //>
  15. bool operator>(const string& s) const
  16. {
  17. return strcmp(_str, s._str) > 0;
  18. }
  19. //==
  20. bool operator==(const string& s) const
  21. {
  22. return strcmp(_str, s._str) == 0;
  23. }
  24. //>=
  25. bool operator>=(const string& s) const
  26. {
  27. return *this > s || *this == s;
  28. }
  29. //<
  30. bool operator<(const string& s) const
  31. {
  32. return !(*this >= s);
  33. }
  34. //<=
  35. bool operator<=(const string& s) const
  36. {
  37. return !(*this > s);
  38. }
  39. //!=
  40. bool operator!=(const string& s) const
  41. {
  42. return !(*this == s);
  43. }

有了[ ]、迭代器,我们可以展示3种遍历方法:下标访问、迭代器区间访问、范围for访问

  1. void Print(const string& s)
  2. {
  3. //下标遍历
  4. for (size_t i = 0; i < s.size(); ++i)
  5. cout << s[i] << " ";
  6. cout << endl;
  7. //迭代器遍历访问
  8. string::const_iterator it = s.begin();
  9. while (it != s.end())
  10. {
  11. cout << *it << " ";
  12. ++it;
  13. }
  14. cout << endl;
  15. //范围for
  16. for (auto &e : s)
  17. cout << e << " ";
  18. cout << endl;
  19. }

四、增删接口(Modifiers)

       字符串的增删接口一般要设置两个版本,一个是操作字符,一个是操作字符串,我们先把最难的insert和erase搞了,其他的就可以复用了

4.1 insert

  1. //指定位置插入一个字符
  2. string& insert(size_t pos, char ch)
  3. {
  4. assert(pos <= _size);
  5. //判断是否需要扩容
  6. if (_size + 1 > _capacity)
  7. reserve(2 * _capacity);
  8. //pos后的数据要往后挪,所以要从后往前移
  9. size_t end = _size + 1;
  10. while (end > pos)
  11. {
  12. _str[end] = _str[end - 1];
  13. --end;
  14. }
  15. _str[pos] = ch;
  16. ++_size;
  17. return *this;
  18. }
  19. //指定位置插入一个字符串
  20. string& insert(size_t pos, const char* str)
  21. {
  22. assert(pos <= _size);
  23. //判断是否需要扩容
  24. size_t len = strlen(str);
  25. if (_size + len > _capacity)
  26. reserve(_size + len);
  27. size_t end = _size + len;
  28. while (end > pos + len - 1)
  29. {
  30. _str[end] = _str[end - len];
  31. --end;
  32. }
  33. //拷贝插入
  34. strncpy(_str + pos, str, len);
  35. _size += len;
  36. return *this;
  37. }

4.2 erase

  1. //删除指定位置之后的字符
  2. string& erase(const size_t pos, size_t len = npos)
  3. {
  4. assert(pos <= _size);
  5. //有两种情况,一种是全删完,一种是删中间的一部分
  6. //全删完
  7. if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出
  8. {
  9. _str[pos] = '\0';
  10. _size = pos;
  11. }
  12. //删一部分
  13. else
  14. {
  15. strcpy(_str + pos, _str + pos + len);
  16. _size -= len;
  17. }
  18. return *this;
  19. }

 len == npos这个判断条件必须写,因为nops已经是无符号最大值了,再+会溢出

4.3 push_back

  1. //尾插一个字符
  2. void push_back(char ch)
  3. {
  4. insert(_size, ch);
  5. }

4.4 append

  1. //尾插一个字符串
  2. string& append(const char* str)
  3. {
  4. return insert(_size, str);
  5. }

4.5 +=重载(用的多)

  1. //+=重载 字符
  2. string& operator+=(char ch)
  3. {
  4. push_back(ch);
  5. return *this;
  6. }
  7. //+=重载 字符串
  8. string& operator+=(const char* str)
  9. {
  10. append(str);
  11. return *this;
  12. }

五、字符串操作(String operations

5.1 获取c类型字符串

  1. //获取c类型字符串
  2. const char* c_str() const
  3. {
  4. return _str;
  5. }

5.2 寻找指定字符串并返回下标

  1. //寻找指定字符串并返回下标
  2. size_t find(const char* str, size_t pos = 0)const
  3. {
  4. assert(pos < _size);
  5. char* p = strstr(_str + pos, str);
  6. if (p == nullptr)
  7. return npos;
  8. return p - _str;
  9. }

六、重载流插入和流提取 

我们不能写在类内,否则会*this会占用第一个操作数,不符合我们的使用习惯。

6.1 流提取

  1. //重载<<
  2. std::ostream& operator<< (std::ostream& out, const string& s)
  3. {
  4. //可以用范围for,也可以用迭代器 范围for是用宏写的,本质上也是迭代器!
  5. for (auto ch : s)
  6. out << ch;
  7. return out;
  8. }

6.2 流插入

首先我们要知道两点,1.>>只会读取到空格或者换行结束 2.读取前会清理掉原空间的数据

  1. //重载>>
  2. std::istream& operator>> (std::istream& in, string& s)
  3. {
  4. //读取前要先清理掉原来存在的字符
  5. s.clear();
  6. //用get获取字符
  7. char ch = in.get();
  8. //先用一个数组存起来,再一起加
  9. char buff[128];
  10. size_t i = 0;
  11. while (ch != ' ' && ch != '\n')
  12. {
  13. //原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加
  14. //s += ch;
  15. buff[i++] = ch;
  16. if (i == 127)
  17. {
  18. buff[127] = '\0';
  19. s += buff;
  20. i = 0;//重置i
  21. }
  22. ch = in.get();
  23. }
  24. //循环结束后可能还要一些字母没有存进去
  25. if (i != 0)
  26. {
  27. buff[i] = '\0';
  28. s += buff;
  29. }
  30. return in;
  31. }

我们用一个buff数组来暂时存储需要插入的字符,等存完了再+=,这样可以提高效率,以空间换时间 

七、string模拟实现全部代码

  1. namespace cyx
  2. {
  3. using std::endl;
  4. using std::cout;
  5. class string
  6. {
  7. public:
  8. //迭代器的实现(可读可写)
  9. typedef char* iterator;
  10. iterator begin()
  11. {
  12. return _str;
  13. }
  14. iterator end()
  15. {
  16. return _str + _size;
  17. }
  18. //迭代器的实现(可读不可写)
  19. typedef const char* const_iterator;
  20. const_iterator begin() const
  21. {
  22. return _str;
  23. }
  24. const_iterator end() const
  25. {
  26. return _str + _size;
  27. }
  28. //交换字符串
  29. void swap(string& s)
  30. {
  31. std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向
  32. std::swap(_size, s._size);
  33. std::swap(_capacity, s._capacity);
  34. }
  35. //构造函数
  36. string(const char* str = "")
  37. :_size(strlen(str))
  38. {
  39. _capacity = _size == 0 ? 3 : _size;
  40. _str = new char[_capacity + 1];// 多开一块\0的空间
  41. strcpy(_str, str);
  42. }
  43. //拷贝构造函数(传统写法)
  44. /*string(const string& s)
  45. :_size(s._size)
  46. , _capacity(s._capacity)
  47. {
  48. _str = new char[_capacity + 1];
  49. strcpy(_str, s._str);
  50. }*/
  51. //拷贝函数的现代写法
  52. string(const string& s)
  53. :_str(nullptr)
  54. {
  55. string temp(s._str);
  56. swap(temp);
  57. }
  58. //迭代器区间构造
  59. template <class InputIterator>
  60. string(InputIterator first, InputIterator last)
  61. :_str(new char[last-first+1])
  62. ,_size(0)
  63. ,_capacity(last-first)
  64. {
  65. while (first != last)
  66. {
  67. push_back (*first);
  68. ++first;
  69. }
  70. }
  71. //赋值重载(传统写法)
  72. string& operator=(const string& s)
  73. {
  74. if (this != &s)//避免自赋值
  75. {
  76. //先开新空间再毁旧空间,避免新空间开失败导致数据丢失
  77. char* temp = new char[s._capacity + 1];
  78. strcpy(temp, s._str);
  79. delete[]_str;
  80. _str = temp;
  81. _size = s._size;
  82. _capacity = s._capacity;
  83. }
  84. return *this;
  85. }
  86. //赋值重载(现代写法)
  87. string& operator=(string s)//必须用值传递,否则会导致原数据的丢失
  88. {
  89. swap(s);
  90. return *this;
  91. }
  92. //析构函数
  93. ~string()
  94. {
  95. delete[]_str;
  96. _str = nullptr;
  97. _size = _capacity = 0;
  98. }
  99. //获取c类型字符串
  100. const char* c_str() const
  101. {
  102. return _str;
  103. }
  104. //获取当前size
  105. size_t size() const
  106. {
  107. return _size;
  108. }
  109. //获取当前capacity
  110. size_t capacity() const
  111. {
  112. return _capacity;
  113. }
  114. //[]重载(可读可写)
  115. char& operator[](size_t pos)
  116. {
  117. assert(pos < _size);//确保地址有效
  118. return _str[pos];
  119. }
  120. //[]重载(可读不可写)
  121. const char& operator[](size_t pos) const
  122. {
  123. assert(pos < _size);//确保地址有效
  124. return _str[pos];
  125. }
  126. //比较类型重载
  127. //>
  128. bool operator>(const string& s) const
  129. {
  130. return strcmp(_str, s._str) > 0;
  131. }
  132. //==
  133. bool operator==(const string& s) const
  134. {
  135. return strcmp(_str, s._str) == 0;
  136. }
  137. //>=
  138. bool operator>=(const string& s) const
  139. {
  140. return *this > s || *this == s;
  141. }
  142. //<
  143. bool operator<(const string& s) const
  144. {
  145. return !(*this >= s);
  146. }
  147. //<=
  148. bool operator<=(const string& s) const
  149. {
  150. return !(*this > s);
  151. }
  152. //!=
  153. bool operator!=(const string& s) const
  154. {
  155. return !(*this == s);
  156. }
  157. //改变size
  158. void resize(size_t n, char ch = '\0')
  159. {
  160. if (n > _size)
  161. {
  162. reserve(n);
  163. memset(_str + _size, ch, n - _size);
  164. }
  165. _size = n;
  166. _str[_size] = '\0';
  167. }
  168. //改变capacity
  169. void reserve(size_t n)
  170. {
  171. if (n > _capacity)
  172. {
  173. char* tmp = new char[n + 1];
  174. strcpy(tmp, _str);
  175. delete[] _str;
  176. _str = tmp;
  177. _capacity = n;
  178. }
  179. }
  180. //尾插一个字符
  181. void push_back(char ch)
  182. {
  183. insert(_size, ch);
  184. }
  185. //尾插一个字符串
  186. string& append(const char* str)
  187. {
  188. return insert(_size, str);
  189. }
  190. //+=重载 字符
  191. string& operator+=(char ch)
  192. {
  193. push_back(ch);
  194. return *this;
  195. }
  196. //+=重载 字符串
  197. string& operator+=(const char* str)
  198. {
  199. append(str);
  200. return *this;
  201. }
  202. //指定位置插入一个字符
  203. string& insert(size_t pos, char ch)
  204. {
  205. assert(pos <= _size);
  206. //判断是否需要扩容
  207. if (_size + 1 > _capacity)
  208. reserve(2 * _capacity);
  209. //pos后的数据要往后挪,所以要从后往前移
  210. size_t end = _size + 1;
  211. while (end > pos)
  212. {
  213. _str[end] = _str[end - 1];
  214. --end;
  215. }
  216. _str[pos] = ch;
  217. ++_size;
  218. return *this;
  219. }
  220. //指定位置插入一个字符串
  221. string& insert(size_t pos, const char* str)
  222. {
  223. assert(pos <= _size);
  224. //判断是否需要扩容
  225. size_t len = strlen(str);
  226. if (_size + len > _capacity)
  227. reserve(_size + len);
  228. size_t end = _size + len;
  229. while (end > pos + len - 1)
  230. {
  231. _str[end] = _str[end - len];
  232. --end;
  233. }
  234. //拷贝插入
  235. strncpy(_str + pos, str, len);
  236. _size += len;
  237. return *this;
  238. }
  239. //删除指定位置之后的字符
  240. string& erase(const size_t pos, size_t len = npos)
  241. {
  242. assert(pos <= _size);
  243. //有两种情况,一种是全删完,一种是删中间的一部分
  244. //全删完
  245. if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出
  246. {
  247. _str[pos] = '\0';
  248. _size = pos;
  249. }
  250. //删一部分
  251. else
  252. {
  253. strcpy(_str + pos, _str + pos + len);
  254. _size -= len;
  255. }
  256. return *this;
  257. }
  258. //寻找指定字符并返回下标
  259. size_t find(char ch, size_t pos = 0)const
  260. {
  261. assert(pos < _size);
  262. for (size_t i = pos; i < _size; ++i)
  263. if (_str[i] == ch)
  264. return i;
  265. return npos;
  266. }
  267. //寻找指定字符串并返回下标
  268. size_t find(const char* str, size_t pos = 0)const
  269. {
  270. assert(pos < _size);
  271. char* p = strstr(_str + pos, str);
  272. if (p == nullptr)
  273. return npos;
  274. return p - _str;
  275. }
  276. //清理字符串
  277. void clear()
  278. {
  279. _str[0] = '\0';
  280. _size = 0;
  281. }
  282. //缩容到他的size
  283. void shrink_to_fit()
  284. {
  285. char* temp = new char[_size + 1];
  286. strcpy(temp, _str);
  287. delete[] _str;
  288. _str = temp;
  289. _capacity = _size;
  290. }
  291. //判断字符串是否为空
  292. bool empty()
  293. {
  294. return _capacity == 0;
  295. }
  296. private:
  297. char* _str;
  298. size_t _size;
  299. size_t _capacity;
  300. static const size_t npos;
  301. //static const size_t npos=-1 vs下int类型支持给static和const同时修饰的变量用缺省值
  302. };
  303. const size_t string::npos = -1;//静态成员遍历类外初始化
  304. //重载<<
  305. std::ostream& operator<< (std::ostream& out, const string& s)
  306. {
  307. //可以用范围for,也可以用迭代器 范围for是用宏写的,本质上也是迭代器!
  308. for (auto ch : s)
  309. out << ch;
  310. return out;
  311. }
  312. //重载>>
  313. std::istream& operator>> (std::istream& in, string& s)
  314. {
  315. //读取前要先清理掉原来存在的字符
  316. s.clear();
  317. //用get获取字符
  318. char ch = in.get();
  319. //先用一个数组存起来,再一起加
  320. char buff[128];
  321. size_t i = 0;
  322. while (ch != ' ' && ch != '\n')
  323. {
  324. //原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加
  325. //s += ch;
  326. buff[i++] = ch;
  327. if (i == 127)
  328. {
  329. buff[127] = '\0';
  330. s += buff;
  331. i = 0;//重置i
  332. }
  333. ch = in.get();
  334. }
  335. //循环结束后可能还要一些字母没有存进去
  336. if (i != 0)
  337. {
  338. buff[i] = '\0';
  339. s += buff;
  340. }
  341. return in;
  342. }
  343. //遍历方法的展示
  344. void Print(const string& s)
  345. {
  346. //下标遍历
  347. for (size_t i = 0; i < s.size(); ++i)
  348. cout << s[i] << " ";
  349. cout << endl;
  350. //迭代器遍历访问
  351. string::const_iterator it = s.begin();
  352. while (it != s.end())
  353. {
  354. cout << *it << " ";
  355. ++it;
  356. }
  357. cout << endl;
  358. //范围for
  359. for (auto &e : s)
  360. cout << e << " ";
  361. cout << endl;
  362. }

有新的后面再补充哦! 

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

闽ICP备14008679号