1、标准库中的string类
学会看文档---学会猜
cplusplus.com/reference/string/string/
比如说这里,有需要再查文档
String类的constuctor(构造)
一、string类常用构造:
(constructor)函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
示例:
#include<iostream> using namespace std; #include<string> void Teststring() { string s1; // 构造空的string类对象s1 string s2("hello world"); // 用C格式字符串构造string类对象s2 string s3(s2); // 拷贝构造s3 string s4(s2, 5); string s5 = s4;//赋值构造 cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; cout << s4 << endl; cout << s5 << endl; } int main() { Teststring(); return 0; }
输出结果:
二、string的遍历
1.第一种(operator[]):
注意区别:
int a[10]
a[i]等价于 *(a+i)
void test_string1() { string s1("hello world"); // 下标+[] for (size_t i = 0; i < s1.size(); i++) { cout << s1[i] << " "; //cout << s1.operator[](i) << " ";//这是一个函数调用 } cout << endl; for (size_t i = 0; i < s1.size(); i++) { s1[i]++; } for (size_t i = 0; i < s1.size(); i++) { cout << s1[i] << " "; } cout << endl; }
运行结果:
其实相当于调用了:
可读可修改
class string //底层是basic_string { public: char& operator[] (size_t pos) { return _str[pos]; } private: char* _str; size_t _size; size_t _capacity; };
string::operator[]函数 构成两个运算符重载,又构成函数重载
构成两个运算符重载,又构成函数重载,
为啥有两个版本 一个普通对象,一个const对象,为了让const对象无法修改
参数类型不同才能构成重载
而这里他们的参数类型就不同,他们隐含的this指针不同
1.string* 2.const string*
const修饰的是this指针指向的内容
string s3("hello world"); s3[0]++; cout << s3 << endl; const string s2("hello world"); //s2[0]++; cout << s2 << endl; cout << s3.size() << endl; cout << s3.capacity() << endl;
运行结果:
这里注意,size()不包括\0
2.第二种(iterator)
迭代器 iterator 在这里是string的一个类型,这个类型是定义在他的类域里面的
所以要指定类域:string::iterator
一般变量名都取名 it
string::iterator it3 = s3.begin(); while (it3 != s3.end()) { cout << *it3 << " "; ++it3; } cout << endl;
迭代器行为是像指针一样类型对象
迭代器也是一个可读写的
string::iterator it3 = s3.begin(); while (it3 != s3.end()) { *it3 -= 3; ++it3; } cout << endl; it3 = s3.begin(); while (it3 != s3.end()) { cout << *it3 << " "; ++it3; } cout << endl;
运行结果:
对比两种遍历:哪种好用?
[]好用
但是,迭代器才是主流
迭代器可以遍历通用的容器(不管底层是数组、是链表还是数)
vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); vector<int>::iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); list<int>::iterator itt = lt.begin(); while (itt != lt.end()) { cout << *itt << " "; ++itt; } cout << endl;
运行结果:
3.第三种(范围for)
范围for可以遍历数组
也可以遍历容器
// 底层就是迭代器 for (auto e : s3) { cout << e << " "; } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; for (auto e : lt) { cout << e << " "; } cout << endl;
运行结果:
在底层的角度看,两组代码是一样的,无区别,因为范围for的底层就是迭代器
在string类中vector,范围for和[]好用
反向迭代器、const迭代器:
反向:
示例代码:
void test_string2() { string s2("hello world"); string::reverse_iterator rit = s2.rbegin(); while (rit != s2.rend()) { cout << *rit << " "; ++rit; } cout << endl; }
运行结果:
类似于这样的原理:
const迭代器
示例:
由于这里是一个const 对象,返回的不是普通迭代器,会报错,应该是有const修饰的
因此应该这样写:
void test_string3() { const string s3("hello world"); string::const_iterator it3 = s3.begin(); while (it3 != s3.end()) { cout << *it3 << " "; ++it3; } cout << endl; }
运行结果:
普通迭代器和const修饰过的迭代器的区别是什么?
普通迭代器可读可写,const修饰的迭代器只可读
因此还有const反向迭代器
const_reverse_iterator(这里不做赘述)
三、string 类的容量
1.size()和length()
void test_string4() { string s1("hello world"); cout << s1.size() << endl; cout << s1.length() << endl; }
输出结果一样:11(都不包含"\0")
但是由于规范,我们最好选择size(),size出现的时间更早,通用取名。
2.max_size()意义不大
cout << s1.max_size() << endl; cout << s1.capacity() << endl;
max_size意义不大,
capacity在这里值为:15
3.扩容capacity()
在vs2022中:
// 查看扩容机制 string s; size_t sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; 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'; } }
运行结果:
实际上,capacity不包含\0占用的空间,因此这里的15实际上是16,第一次扩容二倍,后面都扩容1.5倍
在linux环境下g++:
运行结果:
2倍
总结:STL是一个规范,规定功能,没有规定实现细节
4.clear()清数据,清空数据但是不一定清空间:
清空数据但是不清空间:通过此验证
cout << s1 << endl; s1.clear(); cout << s1 << endl; cout << s1.capacity() << endl; cout << s1.size() << endl;
运行结果:这里只是清除数据但是没有清空间
再比如:
void test_string4() { string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); cout << s1.size() << endl; cout << s1.length() << endl; cout << s1.max_size() << endl; cout << s1.capacity() << endl; // 查看扩容机制 string s; size_t sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; 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'; } } cout << s1 << endl; cout << s1.capacity() << endl; cout << s1.size() << endl; s1.clear(); cout << s1 << endl; cout << s1.capacity() << endl; cout << s1.size() << endl; }
运行结果:
5.shrink_to_fit()缩容
如此:
cout << s1 << endl; cout << s1.capacity() << endl; cout << s1.size() << endl; s1.clear(); cout << s1 << endl; cout << s1.capacity() << endl; cout << s1.size() << endl; // 缩容 s1.shrink_to_fit(); cout << s1.capacity() << endl; cout << s1.size() << endl;
运行结果:
6.reserve()手动扩容
区分:
reserve --- 保留储备,(在这里是用来请求扩容的,手动扩容)
reverse --- 翻转
扩容是有代价的:
扩容会先拷贝数据到一个更大的空间,在销毁原来的数据空间。
什么方式可以不用扩容,使用reserve(),但是前提是我知道要使用多少空间
提前保留一个这样的空间:
void test_string4() { string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); cout << s1.size() << endl; cout << s1.length() << endl; cout << s1.max_size() << endl; cout << s1.capacity() << endl; // 查看扩容机制 string s; s.reserve(100); size_t sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; 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'; } } cout << s1 << endl; cout << s1.capacity() << endl; cout << s1.size() << endl; s1.clear(); cout << s1 << endl; cout << s1.capacity() << endl; cout << s1.size() << endl; // 缩容 s1.shrink_to_fit(); cout << s1.capacity() << endl; cout << s1.size() << endl; }
运行结果:
reserve开100的空间就一定开100吗?在vs中不一定
在linux ---g++中是100
reserve会缩容吗?
不会,比当前capacity大的时候才会扩容
void test_string5() { string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); cout << s1.capacity() << endl; cout << s1.size() << endl; s1.reserve(10); cout << s1.capacity() << endl; cout << s1.size() << endl; }
运行结果:
7.resize()改变size
reserve是改变容量,resize是改变size,该数据,空间不够会扩容吗?不会
resize()有三种情况
10 会删除 n<size
20 会插入 size<n<capacity
40 扩容+插入 capacity<n
void test_string5() { string s2("hello worldxxxxxxx"); cout << s2 << endl; cout << s2.capacity() << endl; cout << s2.size() << endl; cout << "xxxxxxxxxxxxxxxxxxxxxxx" << endl; s2.reserve(200); cout << s2 << endl; cout << s2.capacity() << endl; cout << s2.size() << endl; cout << "xxxxxxxxxxxxxxxxxxxxxxx" << endl; s2.resize(10); cout << s2 << endl; cout << s2.capacity() << endl; cout << s2.size() << endl; }
输出结果:
会保留前十个数据,因此在应用时,如果只想保留比原字符串少的可以用resize来删除多余
此时再:
s2.resize(20); cout << s2 << endl; cout << s2.capacity() << endl; cout << s2.size() << endl;
运行结果:
相当于再插入了10个数据,并且插入的是"\0"
resize的是比capacity大的呢?
四、数据的访问
1.operator[]
类似于遍历。
2.at(类似于operator)
示例代码:
void test_string6() { string s1("hello_hello"); cout << s1[5] << endl; cout << s1.at(5) << endl; }
运行结果:
区别在于越界的检查不同:
当我这样写:
s1[15]; s1.at(15);
断言错误
//s1[15]; s1.at(15);
五、元素的修改
1.push_back在尾部插入字符
void test_string7() { string s1("hello_hello"); //在尾部插入字符 s1.push_back('_'); cout << s1 << endl; }
运行结果:
2.append插入字符串
a.
s1.append("hello"); cout << s1 << endl;
运行结果
b.
void test_string7() { string s1("hello_hello"); //在尾部插入字符 s1.push_back('_'); cout << s1 << endl; s1.append("hello"); cout << s1 << endl; s1.append(10, '!'); cout << s1 << endl; string s2(" apple "); s1.append(s2.begin(), s2.end()); cout << s1 << endl; }
运行结果:
我想让apple空格少一些
s1.append(++s2.begin(), --s2.end());
cout << s1 << endl;运行结果:
3.operator[]对于字符、字符串的插入实践中:我们都用operator[]
void test_string7() { string s3("hello_hello"); s3 += ' '; s3 += "apple"; cout << s3 << endl; }
运行结果:
4-后所有代码:
void test_string8() { string s1("hello_hello"); cout << s1 << endl; s1.assign(" xxxxx"); cout << s1 << endl; s1.insert(0, "yyyy"); cout << s1 << endl; s1.erase(5, 3); cout << s1 << endl; s1.erase(); cout << s1 << endl; string s2("hello world hello hello"); //第五个开始的第一个字符替换成20% /*s2.replace(5, 1, "20%"); cout << s2 << endl;*/ /*size_t pos = s2.find(' '); while (pos != string::npos) { s2.replace(pos, 1, "20%"); pos = s2.find(' '); } cout << s2 << endl;*/ // insert/erase/replace // 能少用就要少用,因为基本都要挪动数据,效率不高 string s3; s3.reserve(s2.size()); for (auto ch : s2) { if (ch != ' ') { s3 += ch; } else { s3 += "20%"; } } cout << s3 << endl; s2.swap(s3); cout << s2 << endl; }
4.assign直接对空间进行覆盖
运行结果:
5.insert
s1.insert(0, "yyyy");
cout << s1 << endl;运行结果:
6.erase(指定位置开始删除)
运行结果:
7.replace指定替换
s2.replace(5, 1, "20%");
String operations:
1.find
如果没有找到就会返回string::npos
这里代码的含义是:
替换所有的空格
在s2中找出所有空格,若找到了用20%替代,没有了就退出循环
string s2("hello world hello hello"); //第五个开始的第一个替换成20% /*s2.replace(5, 1, "20%"); cout << s2 << endl;*/ size_t pos = s2.find(' '); while (pos != string::npos) { s2.replace(pos, 1, "20%"); //替换了再继续查找,找到了再继续替换 pos = s2.find(' '); } cout << s2 << endl;
运行结果:
不替换的写法(消耗空间,但是缩减时间):
//不替换,直接创建新对象赋值 string s3; s3.reserve(s2.size()); for (auto ch : s2) { if (ch != ' ') { s3 += ch; } else { s3 += "20%"; } } cout << s3 << endl; s2.swap(s3); cout << s2 << endl;
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。 在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。