七、Modifiers 修改
🎨追加
+=最好用也最常用,因为既可以追加字符、也可追加字符串 ,其实底层调用了append和push_back
void test_string7() { string s("hello"); s.push_back('-'); s.push_back('-'); s.append("world"); cout << s << endl; string str("我来了"); s += '@';//字符 s += str;//字符串 s += "JDG总冠军"; cout << s << endl; }
其中append的迭代器可以选择性的打印字符串内容(了解即可)
string s("hello"); string str("我来了"); s.append(++str.begin(), --str.end()); string copy(s.begin() + 5, s.end() -5);
下面来研究尾插扩容容量变化 ——
void test_string8() { string s; size_t sz = s.capacity(); cout << "making s grow:\n"; for (int i = 0; i < 100; ++i) { s.push_back('c'); if (sz != s.capacity()) { sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; } } }
我们可以看见在vs下增容,第一次是两倍,后面是1.5倍的增容
如果提前知道是多少空间,可以调用reserve预留空间,避免频繁增容的消耗
s.resize(1000 , 'x'); //开空间+初始化
🎨插入和删除
🐋尽量少使用头部的插入和删除,因为要挪动,O(N)效率低
🤞1️⃣小练习:在字符串中空格的地方插入一个%
我的第一想法:遍历字符串,遇到' '的时候,直接插入
void test_string9() { //在空格的地方插入一个% string str("JDG NB 总冠军"); for (size_t i = 0; i < str.size(); ++i) { if (str[i] == ' ') { str.insert(i, "20%"); } } cout << str << endl; }
但这样有没有问题呢?
那怎么样处理比较好呢?只需要在插入的地方i额外的+3,
void test_string9() { //在空格的地方插入一个% string str("JDG NB 总冠军"); for (size_t i = 0; i < str.size(); ++i) { if (str[i] == ' ') { str.insert(i, "20%"); i += 3; } } cout << str << endl; }
2️⃣练习升级:把字符串中遇到的空格替换成20%
这就要引入erase:删除字符串中的字符
void test_string9() { //在空格的地方插入一个% string str("JDG NB 总冠军"); for (size_t i = 0; i < str.size(); ++i) { if (str[i] == ' ') { str.insert(i, "20%"); i += 3; } } //再把空格删除掉哦 for (size_t i = 0; i < str.size(); ++i) { if (str[i] == ' ') { str.erase(i, 1); } } cout << str << endl; }
ps:最好不要自行挪动数据,因为[]会检查下标位置必须要小于size的,如果真的要挪动,要resize一下,增加长度
也有一种空间换时间的方法:创建新的串遍历,效率O(N)
void test_string9() { string str("JDG NB 总冠军"); string newstr; for (size_t i = 0; i < str.size(); ++i) { if (str[i] != ' ') { newstr += str[i]; } else { newstr += "20%"; } } cout << newstr << endl; }
八、String operations 字符串操作
打印字符串,都能打印,但意义不同 ——
🤞前者是string类的流插入运算符的重载,以对象size为准,size是多少打印多少
后者是以常量字符串对象\0为准,遇到\0就结束(符合c语言标准)
所以说\0不一定是结束标志,在string里会被忽视
主要作用还是与函数接口接合——
string file("test.txt"); FILE* fout = fopen(s.c_str(), "w");//打印文件
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/631a160b51da4d7e9729bbeb05c5295d.png
🌍substr 子串
➰· 取当前串的一个子串
len:如果len比能取到的串长或使用缺省值npos,都是能取多少取多少
🌍查找 find & rfind
🌊 1. 从字符串pos位置从前向后找字符c/字符串,返回该字符在字符串中的位置,找不到就返回npos
🌊 2. 从字符串pos位置倒着找找字符c/字符串,返回该字符在字符串中的位置
💢小练习:取字符串的后缀
void test_string11() { string str("test.cpp"); //找出后缀 size_t pos = str.rfind('.');//面对多后缀的最好用 rfind if (pos != string::npos) { //string buff = str.substr(pos, str.size() - pos); string buff = str.substr(pos);//因为是取到结束 cout << buff << endl; } }
但是这样写有没有说明问题呢?如果后缀是test.cpp.tar.zip呢?要取最后一个后缀
这样就要将find ——》 rfind,即可解决
💢解析出网址的这三个部分:协议 - 域名 - 资源
#include<iostream> #include<string> using namespace std; int main() { string url = "https://cplusplus.com/reference/string/string/substr/"; size_t pos1 = url.find("://"); if (pos1 == string::npos) { cout << "非法字符串" << endl; return; } //取协议 string protocol = url.substr(0, pos1); cout << protocol << endl; size_t pos2 = url.find('/', pos1 + 3); if (pos1 == string::npos) { cout << "非法字符串" << endl; return; } string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);//取域名 cout << domain << endl; string uri = url.substr(pos2 + 1);//取资源 cout << uri << endl; }
💢find 和find_first_of 区别
find_first_of:只要出现要寻找的串里的任意字符都找出来
吐槽一下:find_first_of 更应该叫find_any_of,但我们要尊重语法
九、非成员函数重载
💦流插入&流提取
注意注意:流插入和流提取都是以空格、回车作为结束标志的。这意味着如果想要输入一个字符串,最终可能只读入了一个单词,剩余的留在缓冲区里。
于是我们引入了getline,做题会遇到
💦getline
遇到换行才结束
💢小练习:输出一个整数,表示输入字符串最后一个单词的长度
#include <iostream> #include <string> using namespace std; int main() { string str; getline(cin,str); size_t pos = str.rfind(' '); if(pos != string::npos) { cout<<str.size()-pos-1<<endl; } else{ cout<<str.size()<<endl; } }
💦to_string
void test_string12() { //整形转字符串 int ival; double dval; cin >> ival >> dval; string istr = to_string(ival); string dstr = to_string(dval); cout << istr << endl; cout << dstr << endl; //字符串转整形 istr = "9999"; dstr = "9999.99"; ival = stoi(istr); dval = stod(dstr); }
重要的事情说三遍:不会的查文档,不会的查文档,不会的查文档!!
十、 vs和g++下string结构的说明(了解)
🎉VS下string结构的说明:
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于16时,从堆上开辟空间
union _Bxty { // storage for small buffer or pointer to larger one value_type _Buf[_BUF_SIZE]; pointer _Ptr; char _Alias[_BUF_SIZE]; // to permit aliasing } _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量, 最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。
🎉g++下string的结构
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数
指向堆空间的指针,用来存储字符串。
struct _Rep_base { size_type _M_length; size_type _M_capacity; _Atomic_word _M_refcount; };
📢写在最后
1024程序节,快要来了,大家会怎么样过呢