当前位置:   article > 正文

【C++】————string基础用法及部分函数底层实现_c++string

c++string

 9efbcbc3d25747719da38c01b3fa9b4f.gif

                                                      作者主页:     作者主页

                                                      本篇博客专栏:C++

                                                      创作时间 :2024年6月30日

9efbcbc3d25747719da38c01b3fa9b4f.gif

前言:

 本文主要介绍STL容器之一  ----  string,在学习C++的过程中,我们要将C++视为一个语言联邦(摘录于Effective C++ 条款一)。如何理解这句话呢,我们学习C++,可将其分为四个板块;分别为C、Object-Oriented C++(面向对象的C++)、Template C++(模板)、STL。本文就介绍STL中的string;

一、string是什么?

  string是STL文档的容器之一,是一个自定义类型,是一个类,由类模板basic_string实例化出来的一个类;

我们看一下cplusplus上是咋介绍的?

我们简单看一下即可,下面我来为大家做介绍。

二、string的使用:

  由于string出现的时间实际是早于STL的,是后来划分进STL库的,所以string开始的设计比较冗余,有许多没有必要的接口(一共106个接口函数);这也是被广大C++程序员吐槽的一个槽点,我们无需将每一个接口都记住,我们需要将核心接口记住并熟练使用,遇见一些默认的接口查看文档即可;

2.1构造函数

        在C++98中,string的构造函数一种有如下7种;

  1. int main()
  2. {
  3. // 1、无参默认构造
  4. // string();
  5. string s1;
  6. // 2、拷贝构造
  7. // string (const string& str);
  8. string s2(s1);
  9. // 4、通过字符串常量初始化
  10. // string (const char* s);
  11. string s4("hello world");
  12. // 3、通过字符串子串初始化
  13. // string (const string& str, size_t pos, size_t len = npos);
  14. string s3(s4, 5, 5);
  15. // 5、通过字符串前n个字符初始化
  16. // string (const char* s, size_t n);
  17. string s5("hello wrold", 6);
  18. // 6、用n个字符c初始化字符串
  19. // string (size_t n, char c);
  20. string s6(10, 'x');
  21. // 7、迭代器区间初始化(暂不介绍)
  22. return 0;
  23. }

 其中提一下第三种,pos为子串的位置,len子串的长度,若len大于从子串pos位置开始后面字符总数,则表示初始化到子串结尾即可,比如我们要用 “hello world” 初始化字符串,若pos为6,len为20,则用world初始化字符串s1;len还有一个缺省值npos,其数值为无符号整型的-1,也就是无符号的最大值(无符号无负数);

2.2赋值重载:

赋值重载使string能够用=对string对象重新赋值,string的赋值重载一共有有如下三种;

  1. int main()
  2. {
  3. string tmp("hello world");
  4. string s1;
  5. string s2;
  6. string s3;
  7. // 1、string类进行赋值重载
  8. s1 = tmp;
  9. // 2、使用字符串常量赋值重载
  10. s2 = "hello world";
  11. // 3、使用字符赋值重载
  12. s3 = 'A';
  13. return 0;
  14. }

2.3容量相关接口

 首先介绍如下六个简单一些的接口;

  1. int main()
  2. {
  3. string s1("hello world");
  4. // string中储存的字符个数(不包括\0)
  5. cout << s1.length() << endl;
  6. // 与length功能相同
  7. cout << s1.size() << endl;
  8. // 可以最多储存多少个字符(理论值,实际上并没有那么多)
  9. cout << s1.max_size() << endl;
  10. // string的当前容量
  11. cout << s1.capacity() << endl;
  12. // 当前string对象是否为空
  13. cout << s1.empty() << endl;
  14. // 清空s1中所有字符
  15. s1.clear();
  16. return 0;
  17. }

 其中的length与size两种并无相异,由于string出现的较早,当时没有STL其他容器,先出现了length,后来为了统一接口,于其他容器接口保持一致,因此出现了size;

  1. int main()
  2. {
  3. // reserve 提前开空间(可能会大于指定的大小,因此开空间规则不同)
  4. string s1;
  5. s1.reserve(100);
  6. cout << s1.size() << endl;
  7. cout << s1.capacity() << endl;
  8. // resize 提前开空间并初始化 缺省值为0
  9. string s2;
  10. s2.resize(100);
  11. cout << s2.size() << endl;
  12. cout << s2.capacity() << endl;
  13. s2 += "hhhhhh";
  14. return 0;
  15. }

  我们发现reserve仅仅只是修改capacity,而resize不仅会修改capacity,还会修改size,然后用第二个参数取去初始化新增的区间;当指定大小小于原来空间时,reserve什么都不会做,而resize则会则断大于指定大小后面的区域(在后面补零);

总结:reserve仅改变capacity,resize既改变capacity又改变size;当指定大小小于字符串的size时,resize还可以截断(在后面补 \0 )

2.4迭代器

    迭代器是STL库中的一个特殊的存在,我们可以通过迭代器对string类中的字符进行增删查改; 在string类中,我们可将其视为指针;string类中的迭代器接口有如下几种;

 begin函数返回的是字符串中第一个字符的位置的迭代器,而end函数返回的字符串中最后一个字符的下一个位置的迭代器; 因此遍历一个string类,有一下三种方法;

  1. int main()
  2. {
  3. string s1("hello world");
  4. // 三种遍历方式
  5. // 1、通过[]来访问每一个字符
  6. for (int i = 0; i < s1.size(); i++)
  7. {
  8. cout << s1[i] << " ";
  9. }
  10. cout << endl;
  11. // 2、通过迭代来来访问每一个字符
  12. string::iterator it = s1.begin();
  13. while (it != s1.end())
  14. {
  15. cout << *it << " ";
  16. it++;
  17. }
  18. cout << endl;
  19. // 3、通过范围for(其实范围for就是编译器替换成了迭代器遍历的方法)
  20. for (auto ch : s1)
  21. {
  22. cout << ch << " ";
  23. }
  24. cout << endl;
  25. return 0;
  26. }

     rbegin与rend系列为反向迭代器;rbegin返回的是最后一个字符的位置的迭代器,rend返回的是第一个字符的前一个位置的迭代器;

 我们可以通过反向迭代器,对其逆向遍历;反向迭代器的类型为 string::reverse_iterator;

  1. int main()
  2. {
  3. string s1("hello world");
  4. string::reverse_iterator rit = s1.rbegin();
  5. while (rit != s1.rend())
  6. {
  7. cout << *rit << " ";
  8. rit++;
  9. }
  10. }

2.5下标访问:

    关于元素的访问,也有如下四个接口,最常用的还是方括号; 

  1. int main()
  2. {
  3. // []重载使string可以像字符数组一样访问
  4. string s1("hello world");
  5. cout << s1[0] << endl;
  6. cout << s1[1] << endl;
  7. // at 于[] 功能相同,只不过[]的越界是由assert来限制,而at则是抛异常
  8. cout << s1.at(0) << endl;
  9. cout << s1.at(1) << endl;
  10. // front访问string中第一个字符
  11. cout << s1.front() << endl;
  12. // back访问string中最后一个字符
  13. cout << s1.back() << endl;
  14. return 0;
  15. }

方括号的使用如同数组的方括号使用相同;at与方括号用法相同,只是遇见非法访问时是抛异常解决;

2.6修改

string的修改接口设计得十分冗余;其中我们可以用+=替代append与push_back;实际中,也是+=用得比较多,但是我们还是了解一下相关用法; 

  +=我们可以加等一个string类,可以加等一个字符,也可以加等一个字符指针;因此有以下用法;

  1. int main()
  2. {
  3. string tmp("xxxx");
  4. string s1("hello world");
  5. // += 字符
  6. s1 += ' ';
  7. // += string类
  8. s1 += tmp;
  9. // += 字符指针
  10. s1 += " hello world";
  11. cout << s1 << endl;
  12. }

  1. int main()
  2. {
  3. string tmp("xxxx");
  4. string s1;
  5. // 尾加字符
  6. // void push_back (char c);
  7. s1.push_back('c');
  8. // 尾加string类
  9. // string& append (const string& str);
  10. s1.append(tmp);
  11. // 尾加string从subpos位置开始的sublen个字符
  12. //string& append (const string& str, size_t subpos, size_t sublen);
  13. s1.append(tmp, 2, 3);
  14. // 用字符指针指向的字符串/字符尾加
  15. // string& append (const char* s);
  16. s1.append("hello world");
  17. // 用字符指针指向的字符串的前n个字符尾加
  18. // string& append (const char* s, size_t n);
  19. s1.append("hello world", 6);
  20. // 尾加n个c字符
  21. // string& append (size_t n, char c);
  22. s1.append(5, 'x');
  23. // 迭代器区间追加
  24. // template <class InputIterator>
  25. // string& append(InputIterator first, InputIterator last);
  26. s1.append(tmp.begin(), tmp.end());
  27. cout << s1 << endl;
  28. return 0;
  29. }

 assign为string的赋值函数;是一个扩增版的operator =,用的并不多,主要用法如下;

  1. int main()
  2. {
  3. string tmp("hello world");
  4. string s1;
  5. // 使用string类对其赋值
  6. // string& assign (const string& str);
  7. s1.assign(tmp);
  8. cout << s1 << endl;
  9. // 使用string类中从subpos位置开始的sublen个串来赋值
  10. // string& assign (const string& str, size_t subpos, size_t sublen);
  11. s1.assign(tmp, 2, 5);
  12. cout << s1 << endl;
  13. // 使用字符指针所指向的字符串对其赋值
  14. // string& assign (const char* s);
  15. s1.assign("hello naiths");
  16. cout << s1 << endl;
  17. // 使用字符指针所指向的字符串的前n个对其赋值
  18. // string& assign (const char* s, size_t n);
  19. s1.assign("hello naiths", 7);
  20. cout << s1 << endl;
  21. // 使用n个c字符对其赋值
  22. // string& assign (size_t n, char c);
  23. s1.assign(10, 'x');
  24. cout << s1 << endl;
  25. // 使用迭代器对其赋值
  26. // template <class InputIterator>
  27. // string& assign(InputIterator first, InputIterator last);
  28. s1.assign(tmp.begin(), tmp.end());
  29. cout << s1 << endl;
  30. return 0;
  31. }

  1. int main()
  2. {
  3. string tmp("hello world");
  4. string s1;
  5. // 在pos位置插入string类字符串
  6. // string& insert (size_t pos, const string& str);
  7. s1.insert(0, tmp);
  8. cout << s1 << endl;
  9. // 在pos位置插入str的子串(subpos位置开始的sublen个字符)
  10. // string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
  11. s1.insert(7, tmp, 0, 6);
  12. cout << s1 << endl;
  13. // 在pos位置插入字符指针指向的字符串
  14. // string& insert (size_t pos, constchar* s);
  15. s1.insert(2, "xxx");
  16. cout << s1 << endl;
  17. // 在pos位置插入字符指针指向的字符串的前n个字符
  18. // string& insert (size_t pos, const char* s, size_t n);
  19. s1.insert(7, "hello naiths", 8);
  20. cout << s1 << endl;
  21. // 在pos位置插入n个c字符
  22. // string& insert (size_t pos, size_t n, char c);
  23. s1.insert(0, 5, 'y');
  24. cout << s1 << endl;
  25. // 指定迭代器的位置插入n个字符c
  26. // void insert (iterator p, size_t n, char c);
  27. string::iterator it = s1.begin() + 10;
  28. s1.insert(it, 10, 'z');
  29. cout << s1 << endl;
  30. // 指定迭代器的位置插入字符c
  31. // iterator insert (iterator p, char c);
  32. s1.insert(s1.begin(), 'A');
  33. cout << s1 << endl;
  34. // 指定p位置插入迭代器区间的字符
  35. // template <class InputIterator>
  36. // void insert(iterator p, InputIterator first, InputIterator last);
  37. s1.insert(s1.begin(), tmp.begin() + 3, tmp.begin() + 8);
  38. cout << s1 << endl;
  39. // 删除pos位置开始的len个字符
  40. // string& erase (size_t pos = 0, size_t len = npos);
  41. s1.erase(2, 5);
  42. cout << s1 << endl;
  43. // 删除迭代器位置的那个字符
  44. // iterator erase (iterator p);
  45. s1.erase(s1.begin());
  46. cout << s1 << endl;
  47. // 删除迭代器区间的字符
  48. // iterator erase (iterator first, iterator last);
  49. s1.erase(s1.begin() + 2, s1.begin() + 5);
  50. cout << s1 << endl;
  51. return 0;
  52. }

三、string底层实现

其实对于上面的这些库里面的函数,我们不需要全去记住,记住一些常用的即可,其他的等到我们要用到的时候去cplusplus网站里面找即可,对于下面我们要自己实现的函数,一定要记住,这些函数用的都是比较多的。

  1. #pragma once
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include<iostream>
  4. #include<assert.h>
  5. using namespace std;
  6. namespace bit
  7. {
  8. class string
  9. {
  10. public:
  11. //string();//不传参的情况
  12. string(const string& s);
  13. string(const char* str = "");//这里可以之际全缺省
  14. ~string();
  15. const char* c_str() const;
  16. size_t size() const;
  17. char& operator[](size_t pos);
  18. const char& operator[](size_t pos) const;
  19. string& operator=(const string& s);
  20. //实现一个迭代器
  21. typedef char* iterator;
  22. iterator begin();
  23. iterator end();
  24. typedef const char* const_iterator;
  25. const_iterator begin() const;
  26. const_iterator end() const;
  27. void reserve(size_t n);
  28. void push_back(char ch);
  29. void append(const char* str);
  30. string& operator+=(char ch);
  31. string& operator+=(const char* str);
  32. void insert(size_t pos, char ch);
  33. void insert(size_t pos, const char* str);
  34. void erase(size_t pos, size_t len=npos);
  35. size_t find(char ch, size_t pso = 0);
  36. size_t find(const char* str, size_t pos = 0);
  37. void swap(string& s);
  38. string substr(size_t pos,size_t len);
  39. void clear();
  40. private:
  41. char* _str;
  42. size_t _size;
  43. size_t _capacity;
  44. const static size_t npos;
  45. };
  46. istream& operator>>(istream& is, string& str);
  47. ostream& operator<<(ostream& os, const string& str);
  48. }

上面这些就是我们要实现的一些函数的底层是什么样的,当然,我只是基于自己的理解去写这些,比起C++库里面的肯定要low一点,所以我们理解基础的一些思路即可。

下面我们来看一下这些代码,大家看一下,想要更好的理解可以自己去实现一遍。

  1. #include"string.h"
  2. namespace bit
  3. {
  4. //深拷贝
  5. string::string(const string& s)
  6. {
  7. _str = new char[s._capacity + 1];
  8. strcpy(_str, s._str);
  9. _size = s._size;
  10. _capacity = s._capacity;
  11. }
  12. const size_t string::npos = -1;
  13. //不传参的析构
  14. //string::string()
  15. /*{
  16. _str = new char[1] {'\0'};
  17. _size = 0;
  18. _capacity = 0;
  19. }*/
  20. //构造函数
  21. string::string(const char* str)
  22. //strlen是运行时计算长度,效率比较低,三个strlen重复计算了
  23. //但是如果像下面这样写,还是会出现一些问题,因为初始化列表的初始化是按声明的顺序初始化,这里就会出现问题
  24. /*:_str(new char[strlen(str) + 1])
  25. ,_size(strlen(str))
  26. ,_capacity(strlen(str))*/
  27. : _size(strlen(str))
  28. {
  29. _capacity = _size;
  30. _str = new char[_size + 1];
  31. strcpy(_str, str);
  32. }
  33. //赋值
  34. string& string::operator=(const string& s)
  35. {
  36. //避免自己给自己赋值
  37. if (this != &s)
  38. {
  39. char* tmp = new char[s._capacity + 1];
  40. strcpy(tmp, s._str);
  41. //这样写是为了避免前面的空间大小小于后面的那个
  42. delete[] _str;
  43. _str = tmp;
  44. }
  45. return *this;
  46. }
  47. //析构
  48. string::~string()
  49. {
  50. delete[] _str;
  51. _size = 0;
  52. _capacity = 0;
  53. }
  54. //打印
  55. const char* string::c_str() const//像这种用于打印的函数,可以在后面加上一个const,
  56. //加上const意味着不能修改指向的内容和本事的值,这样即使放生权限的缩小依然不会报错
  57. {
  58. return _str;
  59. }
  60. size_t string::size() const
  61. {
  62. return _size;
  63. }
  64. char& string::operator[](size_t pos)//可读可写
  65. {
  66. assert(pos < _size);
  67. return _str[pos];
  68. }
  69. const char& string::operator[](size_t pos) const
  70. {
  71. assert(pos < _size);
  72. return _str[pos];
  73. }
  74. //迭代器
  75. string::iterator string::begin()
  76. {
  77. return _str;
  78. }
  79. string::iterator string::end()
  80. {
  81. return _str + _size;
  82. }
  83. //无法修改的迭代
  84. string::const_iterator string::begin() const
  85. {
  86. return _str;
  87. }
  88. string::const_iterator string::end() const
  89. {
  90. return _str + _size;
  91. }
  92. void string::reserve(size_t n)
  93. {
  94. if (n > _capacity)
  95. {
  96. char* tmp = new char[n + 1];
  97. strcpy(tmp, _str);
  98. delete[]_str;
  99. _str = tmp;
  100. _capacity = n;
  101. }
  102. }
  103. void string::push_back(char ch)
  104. {
  105. if (_size == _capacity)
  106. {
  107. size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
  108. reserve(newcapacity);
  109. }
  110. _str[_size] = ch;
  111. _str[++_size] = '\0';
  112. }
  113. void string::append(const char* str)
  114. {
  115. size_t len = strlen(str);
  116. if (_size + len > _capacity)
  117. {
  118. reserve(_size + len);
  119. }
  120. strcpy(_str + _size, str);
  121. _size += len;
  122. }
  123. string& string::operator+=(char ch)
  124. {
  125. push_back(ch);
  126. return *this;
  127. }
  128. string& string::operator+=(const char* str)
  129. {
  130. append(str);
  131. return *this;
  132. }
  133. void string::insert(size_t pos, const char* str)
  134. {
  135. assert(pos <= _size);
  136. size_t len = strlen(str);
  137. if (_size + len >= _capacity)
  138. {
  139. reserve(_size + len);
  140. }
  141. int end = _size;
  142. while (end >= pos)
  143. {
  144. _str[end+len] = _str[end];
  145. --end;
  146. }
  147. memcpy(_str + pos, str, len);
  148. _size += len;
  149. }
  150. void string::insert(size_t pos,char ch)
  151. {
  152. if (_size == _capacity)
  153. {
  154. size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
  155. reserve(newcapacity);
  156. }
  157. size_t end = _size;
  158. while (end >= pos)
  159. {
  160. _str[end + 1] = _str[end];
  161. --end;
  162. }
  163. _str[pos] = ch;
  164. }
  165. void string::erase(size_t pos, size_t len)
  166. {
  167. assert(pos <= _size);
  168. //当len大于前面的个数
  169. if (len>=_size-pos)
  170. {
  171. _str[pos] = '\0';
  172. _size = pos;
  173. }
  174. else
  175. {
  176. strcpy(_str + pos, _str + pos + len);
  177. _size -= len;
  178. }
  179. }
  180. size_t string::find(char ch, size_t pos)
  181. {
  182. while (pos!=_size)
  183. {
  184. if (ch == _str[pos])
  185. {
  186. return pos;
  187. }
  188. ++pos;
  189. }
  190. return npos;
  191. }
  192. size_t string::find(const char* str, size_t pos)
  193. {
  194. char* p = strstr(_str + pos, str);
  195. return p - _str;
  196. }
  197. void string::swap(string& s)
  198. {
  199. std::swap(_str, s._str);
  200. std::swap(_capacity, s._capacity);
  201. std::swap(_size, s._size);
  202. }
  203. string string::substr(size_t pos, size_t len)
  204. {
  205. //len大于后面的剩余字符,有多少取多少
  206. if (len > _size - pos)
  207. {
  208. string sub(_str - pos);
  209. return sub;
  210. }
  211. else
  212. {
  213. string sub;
  214. sub.reserve(len);
  215. for (size_t i = 0; i < len; i++)
  216. {
  217. sub += _str[pos + i];
  218. }
  219. return sub;
  220. }
  221. }
  222. void string::clear()
  223. {
  224. _str[0] = '\0';
  225. _size = 0;
  226. }
  227. istream& operator>>(istream& is, string& str)
  228. {
  229. char ch;
  230. is >> ch;
  231. while (ch != ' ' && ch != '\n')
  232. {
  233. str += ch;
  234. is >> ch;
  235. }
  236. return is;
  237. }
  238. ostream& operator<<(ostream& os, const string& str)
  239. {
  240. for (size_t i = 0; i < str.size(); i++)
  241. {
  242. os << str[i];
  243. }
  244. return os;
  245. }
  246. }

最后:

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1.一个冷知识:
屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。

2.你不用变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。
正所谓:君子可内敛不可懦弱,面不公可起而论之。

3.成年人的世界,只筛选,不教育。

4.自律不是6点起床,7点准时学习,而是不管别人怎么说怎么看,你也会坚持去做,绝不打乱自己的节奏,是一种自我的恒心。

5.你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。

最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!

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

闽ICP备14008679号