• [c++基础]-string类


    前言

    作者:小蜗牛向前冲

    名言:我可以接受失败,但我不能接受放弃

     如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

    目录

    一、我们为什么要学习string 

    二、怎么样去学习string 

    1、string类常见的构造函数

    2、string类对象的容量操作

    3、string类对象的访问及遍历操作

    4、string类对象的修改操作

    5、 string类非成员函数

    三、对string进行模拟


    一、我们为什么要学习string 

    我们都知道C语言为我们提供了许多有关字符串的库函数,但是这些都不符合C++类的模式,于是C++的官方就为我们通过了string类,方便我们解决有关字符串的问题。

    二、怎么样去学习string 

    下面我将带领大家去认识sring类中的成员函数及其相关知识。

    1、string类常见的构造函数

    有时候我们要对string类在字符串进行初始化就要用到构造函数

    (constructor)函数名称

    功能说明

    string() (重点)

    构造空的string类对象,即空字符串

    string(const char* s) (重点)

    用C-string来构造string类对象

    string(size_t n, char c)

    string类对象中包含n个字符c

    string(const string&s) (重点)

    拷贝构造函数

    下面我们在代码中理解一下:

    1. //string 的构造函数
    2. void test1()
    3. {
    4. string s1;//构造空的string类对象,即空字符串
    5. string s2("hello world");//用C-string来构造string类对
    6. string s3(s2);//拷贝构造s3
    7. cout << s1 << endl;
    8. cout << s2 << endl;
    9. cout << s3 << endl;
    10. }

     这里我们可以发现s1对象由于我们没有传参所以,被初始化为空字符。

    2、string类对象的容量操作

    其实对于string类中存放字符的数据结构,我们可以理解为是一个顺序表,在下面模拟实现的时候将会得以体现。

    函数名称

    功能说明

    size(重点)

    返回字符串有效字符长度

    length

    返回字符串有效字符长度

    capacity

    返回空间总大小

    empty (重点)

    检测字符串释放为空串,是返回true,否则返回false

    clear (重点)

    清空有效字符

    reserve (重点)

    为字符串预留空间

    resize (重点)

    将有效字符的个数该成n个,多出的空间用字符c填充

     代码理解:

    1. //string的容量
    2. void test2()
    3. {
    4. string s1("hello world");
    5. cout << s1.size() << endl;//字符有效长度
    6. cout << s1.length() << endl;//字符有效长度
    7. cout << s1.capacity() << endl;//返回空间总大小
    8. cout << s1.empty() << endl;//检测字符串释放为空串,是返回true,否则返回false
    9. string s2;
    10. s2.reserve(20);//为字符串预留空间
    11. cout << s2.capacity() << endl;
    12. s1.resize(20, '*');
    13. cout << s1 << endl;
    14. }

     看到上面那么多容量接口,对于有些接口我们不免有一些疑问,比如reserve这个提前开空间有什么用,我们让他们自己进行扩容不可以吗?但是我们利用reserve提高插入数据的效率,避免增容带来的开销,所以说reserve是有必要存在的。

     注意: 

    1. . size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
    2. clear()只是将string中有效字符清空,不改变底层空间大小。
    3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
    4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。

     

    这里建议大家,如果遇到不明白的函数,其实我们是可以是相关的C++网站是读文档的,这样有利于培养我们对c++知识学习能力的自学能力。(博主一般在cpluspius网站上进行相关资料的查找)。

    我举个例子:当我们不明白resize这的是干嘛的,我们就可以在网站是输入string 类,在找到这个类的容量文档(capacity)找到resize就可以查找了。

    3、string类对象的访问及遍历操作

    函数名称

    功能说明

    operator[] (重 点)

    返回pos位置的字符,const string类对象调用

    begin+ end

    begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器

    rbegin + rend

    begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

    范围for

    C++11支持更简洁的范围for的新遍历方式

    下面我将写出三种遍历方式来理解在string类中遍历方式:

    1. void test3()
    2. {
    3. string str1("1234");
    4. //遍历的三种方式
    5. //1 .下标遍历
    6. for (size_t i = 0;i < str1.size();++i)
    7. {
    8. //重载等价于:str1.operator[](i)+1
    9. str1[i]++;
    10. }
    11. cout << str1 << endl;
    12. //2.范围for
    13. for (auto& ch : str1)
    14. {
    15. ch--;
    16. }
    17. cout << str1 << endl;
    18. //反转
    19. size_t begin = 0,end = str1.size() - 1;
    20. while (begin < end)
    21. {
    22. swap(str1[begin++], str1[end--]);
    23. }
    24. //cout << str1 << endl;
    25. //3.迭代器 --通过访问的形式
    26. //string::iterator it1 = str1.begin();
    27. auto it1 = str1.begin();
    28. while (it1 != str1.end())
    29. {
    30. *it1 += 1;
    31. ++it1;
    32. }
    33. it1 = str1.begin();
    34. while (it1 != str1.end())
    35. {
    36. cout << *it1 << " ";
    37. ++it1;
    38. }
    39. cout << endl;
    40. }

    4、string类对象的修改操作

    这里我们要借助string的成员函数完成对字符串的修改:

    函数名称

    功能说明

    push_back

    在字符串后尾插字符c

    append

    在字符串后追加一个字符串

    operator+= (重点)

    在字符串后追加字符串str

    c_str(重点)

    返回C格式字符串

    find + npos(重点)

    从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

    rfind

    从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置

    substr

    在str中从pos位置开始,截取n个字符,然后将其返回

    我们在对string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

    下面演示一下:

    1. //验证+=
    2. void test4()
    3. {
    4. string s("hello");
    5. cout << s << endl;
    6. s += " world";
    7. cout << s << endl;
    8. }

     append和push back都可以达到类似的效果,但是我们通常习惯用+=。

    对于c str这个成员函数其实就是返回字符串首地址(也就是数组的首地址)

    而find就是在字符串中找到某个字符的在顺序表(认为字符串是用顺序表存放的)中的下标:

    5、 string类非成员函数

     这些函数虽然不是string的成员函数,但是都是和字符串相关的,我们在OJ刷题的时候可能会用到,这里我们也要认识一下!

    函数

    功能说明

    operator+

    尽量少用,因为传值返回,导致深拷贝效率低

    operator>> (重点)

    输入运算符重载

    operator<< (重点)

    输出运算符重载

    getline (重点)

    获取一行字符串

    relational operators (重点)

    大小比较

    对于重载+其实他的本质就是调用拷贝构造:

    1. //测试非string的成员函数
    2. void test5()
    3. {
    4. string s1("hello");
    5. string s2(" world");
    6. cout << s1 + s2 << endl;
    7. }

    这里的string的非成员函数还重载了流入和流提取 ,不是库中有吗?为什么我们还要重载呢?

    这是因为库中的实现的并没有针对性,实现的可能不符合我们要输入或者输出自定义类型,所以我们可能重载出我们符合自己要求的流插入和流提取。(在下面我们会模拟实现<<和>>大家可以细细体会)

    对于getline这有什么用了,下面我们来看到一个OJ题

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. string line;
    6. while(getline(cin,line))
    7. {
    8. size_t pos = line.rfind(' ');
    9. cout<size()-pos-1<
    10. }
    11. return 0;
    12. }

    这里我们可以看到,我们并没有用流提取来获取字符串,这是因为流提取默认提取到空格或者换行就结束了,而对于一个字符串中可能存在空格,这样的话就不能用>>而要用getline或取一行字符。

    好了对库中的string我们就先认识到这里,其他的还需要我们学会自己去查文档。

    三、对string进行模拟

    模拟实现中有许多细节,大家要细细观看噢!

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. namespace pjb
    8. {
    9. class string
    10. {
    11. typedef char* iterator;//重命名字符指针
    12. iterator begin()
    13. {
    14. return _str;
    15. }
    16. iterator end()
    17. {
    18. return _str + _size;
    19. }
    20. public:
    21. //这里要注意""这里是个字符串常量,'\0'是一个字符常量,\0是个整形常量
    22. //构造函数
    23. string(const char* str = "")
    24. {
    25. _size = strlen(str);
    26. _capacity = _size;
    27. _str = new char[_capacity + 1];//多开一个空间放'\0'
    28. }
    29. //拷贝构造s2(s1)
    30. string(const string& s)
    31. {
    32. _str = new char[s._capacity + 1];//为s2开辟一个s1一样大小的空间
    33. _capacity = s._capacity;
    34. _size = s._size;
    35. strcpy(_str, s._str);
    36. }
    37. //析构函数
    38. ~string()
    39. {
    40. delete[] _str;
    41. _str = nullptr;
    42. _size = _capacity = 0;
    43. }
    44. //重载赋值 s1 = s3
    45. string& operator=(const string& s)
    46. {
    47. if (this != &s)//避免this和自己赋值
    48. {
    49. char* tmp = new char[s._capacity + 1];
    50. strcpy(tmp, s._str);
    51. delete[]_str;
    52. _str = tmp;
    53. _size = s._size;
    54. _capacity = s._capacity;
    55. }
    56. return *this;
    57. }
    58. //返回字符串的首地址
    59. const char* c_str()const
    60. {
    61. return _str;
    62. }
    63. //返回字符串的长度
    64. size_t size()const
    65. {
    66. return _size;
    67. }
    68. //返回容量
    69. size_t capacity()const
    70. {
    71. return _capacity;
    72. }
    73. //清理
    74. void clear()
    75. {
    76. _size = 0;
    77. _str[0] = '\0';
    78. }
    79. //重载[]
    80. char& operator[](size_t pos)
    81. {
    82. assert(pos < _size);
    83. return _str[pos];
    84. }
    85. const char& operator[](size_t pos) const
    86. {
    87. assert(pos < _size);
    88. return _str[pos];
    89. }
    90. //开指定的容量
    91. void reserve(size_t n)
    92. {
    93. if (n > _capacity)
    94. {
    95. char* tmp = new char[n + 1];
    96. strcpy(tmp,_str);
    97. delete[] _str;
    98. _capacity = n;
    99. }
    100. }
    101. //Resize string
    102. void resize(size_t n, char ch = '\0')
    103. {
    104. if (n > _size)
    105. {
    106. reserve(n);
    107. for (size_t i = _size;i < n;++i)
    108. {
    109. _str[i] = ch;
    110. }
    111. _size = n;
    112. _str[_size] = '\0';
    113. }
    114. else
    115. {
    116. _str[n] = '\0';
    117. _size = n;
    118. }
    119. }
    120. //尾插
    121. void Push_Back(char ch)
    122. {
    123. //判断是否要扩容
    124. if (_size == _capacity)
    125. {
    126. size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
    127. reserve(newCapacity);
    128. }
    129. _str[_size] = ch;
    130. ++_size;
    131. _str[_size] = '\0';
    132. }
    133. //追加到字符串
    134. void append(const char* str)
    135. {
    136. size_t len = strlen(str);
    137. if (_size + len > _capacity)
    138. {
    139. reserve(_size + len);
    140. }
    141. strcpy(_str + _size, str);//这里拷贝的时候要注意拷贝的起始位置
    142. _size += len;
    143. }
    144. //这里注意+=可能会有二种传参
    145. string& operator+=(char ch)
    146. {
    147. Push_Back(ch);
    148. return *this;
    149. }
    150. string& operator+=(const char* str)
    151. {
    152. append(str);
    153. return *this;
    154. }
    155. //插入数据
    156. string& insert(size_t pos, char ch)
    157. {
    158. assert(pos <= _size);
    159. //判断是否要扩容
    160. if (_size == _capacity)
    161. {
    162. size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
    163. reserve(newCapacity);
    164. }
    165. //挪动数据
    166. size_t end = _size + 1;
    167. while (end > pos)
    168. {
    169. _str[end] = _str[end - 1];
    170. end--;
    171. }
    172. _str[pos] = ch;
    173. ++_size;
    174. return *this;
    175. }
    176. //插入字符串
    177. string& insert(size_t pos, const char* str)
    178. {
    179. size_t len = strlen(str);
    180. if (_size + len > _capacity)
    181. {
    182. reserve(_size + len);
    183. }
    184. //挪动数据
    185. size_t end = _size + len;
    186. while (end > pos+ len - 1)
    187. {
    188. _str[end] = _str[end - len];
    189. --end;
    190. }
    191. //拷贝从字符串中删除字符
    192. strncpy(_str + pos, str, len);
    193. _size += len;
    194. return *this;
    195. }
    196. //从字符串中删除字符
    197. string& erase(size_t pos, size_t len = npos)
    198. {
    199. assert(pos < _size);
    200. //当len很大或者直接删完
    201. if (len == npos || len >= _size - pos)
    202. {
    203. _str[pos] = '\0';
    204. _size = pos;
    205. }
    206. else
    207. {
    208. strcpy(_str + pos, _str + pos + len);
    209. _size -= len;
    210. }
    211. return *this;
    212. }
    213. //查找
    214. size_t find(char ch, size_t pos = 0)const
    215. {
    216. assert(pos < _size);
    217. while (pos < _size)
    218. {
    219. if (_str[pos] == ch)
    220. return pos;
    221. ++pos;
    222. }
    223. return npos;//没找到
    224. }
    225. //查找字符串
    226. size_t find(const char* str, size_t pos = 0)const
    227. {
    228. assert(pos < _size);
    229. const char* ptr = strstr(_str + pos, str);//用strstr暴力查找
    230. if (ptr == nullptr)
    231. {
    232. return npos;
    233. }
    234. else
    235. {
    236. return ptr - _str;
    237. }
    238. }
    239. private:
    240. char* _str;
    241. size_t _size;
    242. size_t _capacity;
    243. const static size_t npos = -1;
    244. };
    245. //重载<<和>>
    246. ostream& operator<<(ostream& out, const string& s)
    247. {
    248. for (size_t i = 0; i < s.size(); ++i)
    249. {
    250. out << s[i];
    251. }
    252. return out;
    253. }
    254. istream& operator>>(istream& in, string& s)
    255. {
    256. s.clear();
    257. char buff[128] = { '\0' };
    258. size_t i = 0;
    259. char ch = in.get();
    260. while (ch != ' ' && ch != '\n')
    261. {
    262. if (i == 127)
    263. {
    264. s += buff;
    265. i = 0;
    266. }
    267. buff[i++] = ch;
    268. ch = in.get();
    269. }
    270. if (i > 0)
    271. {
    272. buff[i] = '\0';
    273. s += buff;
    274. }
    275. return in;
    276. }

  • 相关阅读:
    go语言并发实战——日志收集系统(四) 利用tail包实现对日志文件的实时监控
    docker安装MySQL 5.7
    ArcGIS无法开始编辑TIN!开始编辑TIN显示灰色
    【数字电路与系统】【北京航空航天大学】实验:时序逻辑设计——三色灯开关(三)、功能仿真测试
    游戏思考18:AOI视野同步算法介绍和简单实现(未完待续8/3)
    隧道未来如何发展?路网全息感知,颠覆公路交通安全史
    机器学习实战—无监督学习之聚类
    houdini布料解算 质感像塑料
    『吴秋霖赠书活动 | 第四期』《Spring Boot微服务实战》
    并发、线程简单理解
  • 原文地址:https://blog.csdn.net/qq_61552595/article/details/127753078