【C++】string类使用详解
目录
💕1.string类
💕3. String基本功能(1)(2)讲解
💕4.sting类基本功能(3) 讲解
💕5.string类基本功能(4) (拷贝构造函数)
💕6.string类基本功能(5)(6)
💕7.string隐式类型转化
💕8. string类成员函数-size函数
💕9.string类的运算符重载-[ ]
💕10. string类size函数进阶
💕11.string类的关键字iterator(begin函数和end函数)及遍历string类
💕12.迭代器的类型(const与否)
💕13.string类的rbegin函数和rend函数
💕14.string类的sort排序函数
💕15. string类的push_back函数和append函数
💕16.string类的重载+= (尾插)
💕17. string类的assign函数
💕18.string类的insert函数
💕19.string类的erase函数
💕20. string类中的replace函数
💕21.string类中的capacity函数
💕22.reverse函数(非string类中的)
💕23.string类中的reserve函数
💕24.string类中的resize函数
💕25.string类中的find函数与rfind函数
💕26.string类中的substr函数
💕27.string中的find_first_of函数
💕28.string中的find_last_of函数
💕29.string重载操作符+,<
💕30.完结
一切的努力并非白费,偏执的种子扎根在心血的泥土中,也开出暗色的花
(最新更新时间——2025.1.23——各位小年快乐)
💕1.string类
在C++中,string是一种C++的类,即字符串类,同时它也是一种字符数组的顺序表
它包含一个头文件<string>,即->:
本文所有内容均在这里->:string函数
#include<string>
接下来我们将讲解string类的7个功能,链接在这里->:
Cplusplus
💕3. String基本功能(1)(2)讲解
在翻阅string类时,我们可以在Cplusplus中,访问到这样的说明
这些说明string类中存在构造函数,析构函数与赋值运算符重载,因为是string本质是一个类,所以存在这些也很合理,但是这些并不是没有作用的,这些决定了我们用string初始化时的写法,请看代码->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; void test_string1() { // 功能1 string s1;//创建变量s1,并自动调用String的无参构造函数 //功能4 string s2("hello world");//创建变量s1,并自动调用String的有参构造函数 //功能2 string s3(s2);//创建变量s1,并自动调用String的拷贝构造函数 }
我们这里稍微总结一下,就是string类具有构造函数
同时,当我们再次观看Cplusplus时,我们可以找到下图的内容->:
C++中的string类中重载了输入和输出流,因此我们可以cout与cin进行输入和输出类的类型
如下图->:
此处总结用法->:string类具备构造函数(所有种的构造函数)
💕4.sting类基本功能(3) 讲解
接下来讲的是第三个点
第三点可以理解为一个复制的功能
我们可以看到,第一个的参数是一个string类的变量,第二个参数代表位置,第三个参数是长度,单位是字符
代码图片如下->:
我们需要看的是s4的位置
s4的意思是,从s3中下标3的位置开始,复制5个字符的内容,为什么是下标3呢?
因为string是一种字符数组的顺序表,第一个下标为0
当然,这样写的办法很不爽,因为每次都需要创建两个string类型才可以复制,这时候就需要用到隐式类型转换了,因为隐式类型的转换,我们也可以像下面这样写
第三个还需要一些额外的补充,如下图
可以看见,如果我们没传最后一个参数,那么 npos的默认值是-1,但因为size_t是一个无符号的,计算机内存储的又是补码,这就导致这其实是一个42亿多的数,那问题来了,如果我们在s4中,不给第三个参数,会怎么样呢?
可以看到没有报错,而是从下标3开始将后面的内容全部复制了一遍
如果第三个参数什么给一个过大的数呢?
可以看见效果跟不给是一样的,都是讲后面的值全部复制了一遍
💕5.string类基本功能(4) (拷贝构造函数)
第四点其实就是string初始化的写法,即调用构造函数
💕6.string类基本功能(5)(6)
第五条和第六条很容易理解,第五条是从字符串的首元素地址开始存储n个字节
第六条是将n个字符存储进string类的对象中
💕7.string隐式类型转化
我们分析一下下面的代码->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; void test_string2() { // 隐式类型转换 string s2 = "hello world"; const string& s3 = "hello world"; }
我们逐一分析->:
string s2时,"hello world"的类型是const char*,所以会存在隐式类型转换,即调用string类的构造函数,将"hello world'变为string类型的临时变量tep,又因为string s2 = tep,又会调用一次拷贝构造函数,我们在类与对象时讲过,一个表达式时,构造函数+拷贝构造函数会优化为直接构造,即,将"hello world'直接作为参数传给s2的构造函数,所以虽然逻辑上是权限的放大,但其实不会发生,属于权限的平移
const string& s3 = "hello world'时,依旧,"hello world'会发生一次构造函数生成一个string类的临时值tep,而s3会直接变为临时变量的别名,因为临时变量具有常性,即不可修改,所以需要用const修饰,否则就会权限的放大
💕8. string类成员函数-size函数
在cplusplus中,我们可以找到string类中,存在一个成员函数->:size函数
超链接地址->:size函数
string中的size函数是用来返回字符串有效字符长度的,我们用代码来简单的示例
我们知道,字符串是带'\0'的,size返回的就是'\0'之前的有效值
string中还有一种max_size( )函数,它返回的是最长可以容纳的字符串为多长,这里简单认识一下即可->:
💕9.string类的运算符重载-[ ]
C++中,string类实现了operator[ ]的重载,不过有两种,一种是const修饰的,一种是没被const修饰的,这两种有什么区别吗?我们下面讲
首先,它大致的样子是下面的->:
class string { public: // 引用返回 // 1、减少拷贝 // 2、修改返回对象 char& operator[](size_t i) { assert(i < _size); return _str[i]; //这里包含了解引用 } private: char* _str; size_t _size; size_t _capacity; };
它可以让我们像使用数组一样,多说无益,我们实际用一下
如下图,我们可以像数组一样,直接修改它的值
💕10. string类size函数进阶
请看下面的代码->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; void test_string3() { string s1("hello world"); for (size_t i = 0; i < s1.size(); i++) { s1[i]++; } cout << endl; // 越界检查 //s1[20]; for (size_t i = 0; i < s1.size(); i++) { //cout << s1.operator[](i) << " "; cout << s1[i] << " "; } cout << endl; const string s2("hello world"); // 不能修改 //s2[0] = 'x'; //这里不能修改请看图解 } int main() { test_string3(); }
我们可以用size实现string类的遍历,需要额外注意的是,s1[20]是越界访问,在C语言中,如果越界访问,可能会检查不出来,但是如果在string类中,我们越界访问时,会直接报错,终止运行,各位可以试一试
关于不能修改(有些编译器可以),是这样理解的,因为operator[ ]的重载有两种,一种为const修饰,另一种没有const修饰,因此->:
当编译器识别出,哦,s2是const修饰的,那我调用operator[ ]时,就会去匹配const修饰的operator[ ]重载,那s2[0]的类型是const char&类型的,所以不能修改
💕11.string类的关键字iterator(begin函数和end函数)及遍历string类
我们学习了size和operator[ ]后,可以运用它们两个实现一种遍历,但其实遍历可以有三种形式,我们先看代码,后进行讲解->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; void test_string4() { string s1("hello world"); // 遍历方式1:下标+[] for (size_t i = 0; i < s1.size(); i++) { cout << s1[i] << " "; } cout << endl; //遍历方式2: 迭代器 //auto it1 = s1.begin(); string::iterator it1 = s1.begin(); while (it1 != s1.end()) { cout << *it1 << " "; ++it1; } cout << endl; //遍历方式3: 范围for // 底层角度,他就是迭代器 cout << s1 << endl; for (auto& e : s1) { e++; cout << e << " "; } cout << endl; cout << s1 << endl; //cout << typeid(it1).name() << endl; } int main() { test_string4(); }
相信很多人都注意到了方法2,那么方法2的迭代器是什么呢?
其实迭代器是一种很好的遍历方法,它不仅仅可以应用于string类,还可以用于顺序表等
我们先讲这里迭代器的问题,我们先看begin与end函数
超链接地址->:
begin函数 end函数
我们可以这么认为,begin( )函数返回的是字符串的首字符地址,而end( )函数返回的是字符串最后一个有效元素后一个的地址,即'\0'的地址,但其实是不对的
(这里仅仅是可以这么认为)
但其实上官方理解是这样解释的->:
begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
因此,begin与end函数返回的类型是迭代器类型,即iterator类型(迭代器类型)
依旧包含非const与const类型
关键字iterator——迭代器的类型
接下来,我们需要记住一个新的关键字->:iterator,这是迭代器类型
我们要调用string中的迭代器,并手动创建一个变量it1,来读取begin返回的第一个字符的迭代器,如果记不住关键字iterator的,可以使用auto关键字来自己识别类型
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; void test_string4() { string s1("hello world"); //遍历方式2: 迭代器 //iterator是迭代器的意思 string::iterator it1 = s1.begin(); //string::iterator是在说明,在使用string类的迭代器 //auto it1 = s1.begin(); while (it1 != s1.end()) { cout << *it1 << " "; ++it1; } cout << endl; } int main() { test_string4(); }
紧接着用end( )函数获取最后一个字符下一个位置的迭代器,即'\0',当我们没读到'\0'时,这里省略了,循环没什么讲的
💕12.迭代器的类型(const与否)
看下面的代码,讲解写在代码里->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; void test_string5() { const string s1("hello world"); //s1只读不修改,则s1.begin()匹配const类型的,返回值则是const_iterator //string::const_iterator it1 = s1.begin(); auto it1 = s1.begin(); while (it1 != s1.end()) { //不能修改 //*it1 += 3; cout << *it1 << " "; ++it1; } cout << endl; } int main() { test_string5(); }
此处总结->:
可以理解为,iterator是既可以读又可以写,const iterator不能修改迭代器类型的变量(在上述代码中即it1),const_iterator不能修改指向的数据(*it1)
💕13.string类的rbegin函数和rend函数
rbegin和rend与begin和end差不多,只不过rbegin和rend的位置是颠倒的
可以认为,rbegin指向的是字符串的最后一个有效数据,rend指向的是字符串的第一个有效数据,而且rbegin++实际上进行的是正常--的操作,即倒着来的,我们可以用代码运行示范一下->:
这里值得注意的是,rbegin函数与rend函数返回的类型是reverse_iterator,而不是iterator,这与begin和end函数是不同的
💕14.string类的sort排序函数
说明:该函数的主要目的是对string中的字符进行排序
如果我们想进行字符串的字典序排序,我们可以使用string类中的sort函数
这个函数是一个左闭区间,右开区间的函数,具体怎么用,请看下面->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include <algorithm> using namespace std; void test_string6() { string s1("hello world"); cout << s1 << endl; //s1按字典序排序 sort(s1.begin(), s1.end()); //第一个和最后一个参与排序 sort(++s1.begin(), --s1.end()); //前5个排序 [0, 5) sort(s1.begin(), s1.begin()+5); cout << s1 << endl; } int main() { test_string6(); }
需要注意的是,sort函数的使用需要新增一个头文件,不过这个不需要特殊记忆,报错点一下即可
#include <algorithm>
💕15. string类的push_back函数和append函数
说明: string类的对象可以直接使用push_back( )函数和append()函数进行字符串的尾插
push_back()函数只可以一次插入一个字符,而append()函数一次可以插入一串字符
代码如下->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include <algorithm> using namespace std; int main() { string s1("hello world"); s1.push_back('x'); s1.append("pppppp"); cout << s1; }
💕16.string类的重载+= (尾插)
说明:在string中,实施了operator+=重载,它的功能与push_back和append类似,可以理解为是它们两个的结合体,既可以尾插一个又可以尾插一堆,代码如下->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include <algorithm> using namespace std; int main() { string s1("hello world"); s1 += 'x'; s1 += "pppp"; cout << s1; }
💕17. string类的assign函数
说明:string类中涵盖了assign函数,它的功能是允许你将一个新的字符串或字符串的一部分赋给现有的字符串对象,替换原有的内容,具体请看代码->:
int main() { string s1("hello world"); cout << s1 << endl; s1.assign("111111"); cout << s1 << endl; }
无论原来的字符串有多长,都会替换掉新的字符串
💕18.string类的insert函数
说明:insert函数用于在字符串的指定位置插入字符或字符串
代码及效果如下->:
int main() { string s1("hello world"); // 慎用,因为效率不高 -> O(N) // 实践中需求也不高 string s2("hello world"); s2.insert(0, "xxxx"); cout << s2 << endl; char ch = 'y'; cin >> ch; //图中输入为apple s2.insert(0, 1, ch); cout << s2 << endl; s2.insert(s2.begin(), 'y'); cout << s2 << endl; s2.insert(s2.begin(), s1.begin(), s1.end()); cout << s2 << endl; }
注意->:insert函数的使用需要慎重,因为string也是一种字符数组顺序表,每次使用insert插入,后面的内容就需要移动, 时间复杂度很大
💕19.string类的erase函数
erase函数就像橡皮,是用来删除string类中的内容的,但需要注意的是,与insert函数一样,效率不高,每次删除都需要向前移动内容,因此时间复杂度很大
int main() { string s1("hello world"); cout << s1 << endl; // erase效率不高,慎用,和insert类似,要挪动数据 s1.erase(0, 1); //在下标0的位置开始删1个字符的内容 cout << s1 << endl; //等价于s1.erase(5);因为不传默认为npos,即42亿 s1.erase(5, 100); //在下标5的位置开始删100个字符的内容 cout << s1 << endl; }
💕20. string类中的replace函数
replace的函数功能很多,这里就示范一种常用的
int main() { // replace效率不高,慎用,和insert类似,要挪动数据 string s2("hello world"); s2.replace(5, 1, "%20"); //从下标5开始,将1个字符的内容换成"%20" cout << s2 << endl; }
因此,我们可以利用replace,将字符串中空格的位置全部换成%20
int main() { string s3("hello world hello work"); for (size_t i = 0; i < s3.size(); ) { if (s3[i] == ' ') { s3.replace(i, 1, "%20"); i += 3; } else { i++; } } cout << s3 << endl; string s4("hello world hello work"); string s5; for (auto ch : s4) { if (ch != ' ') { s5 += ch; } else { s5 += "%20"; } } cout << s5 << endl; }
注意->:效率不高,因为每次替换都需要移动其他字符
💕21.string类中的capacity函数
我们知道,string类是一种字符数组顺序表,所以它是有容量的,因此,string类中提供了capacity函数,可以供我们查看容量是多少,代码如下->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> using namespace std; void TestPushBack() { string s("111"); // 知道需要多少空间,提前开好 /*s.reserve(200); s[100] = 'x';*/ size_t sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; cout << "making s grow:\n"; for (int i = 0; i < 200; ++i) { s.push_back('c'); if (sz != s.capacity()) { sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; } } } int main() { TestPushBack(); }
我们可以看到第一次开辟为2倍,后几次开辟都为1.5倍,而且,编译器实际上的capacity,会比使用capacity函数打印出来的多一个,这多一个是用来存放'\0'的。至于为什么是1.5倍,这方面没有规定,取决于编译器
💕22.reverse函数(非string类中的)
reverse函数实现的是一个将字符串逆置的功能,代码及效果如下->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<algorithm> using namespace std; int main() { string s1("hello world"); cout << s1 << endl; reverse(s1.begin(),s1.end());//逆转,左闭右开的区间 cout << s1 << endl; }
它需要包含的头文件是
#include<algorithm>
效果图->:
💕23.string类中的reserve函数
string类中的reserve函数用于string类对象的内存开辟,及开辟capacity,它只有一个参数,那就是字符数
代码及效果图->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<algorithm>; using namespace std; int main() { string s1("hello world"); s1.reserve(200);//开辟200个字节的空间 //开辟出来的空间不可以直接使用 s1[100] = 'x';//这里运行直接报错 }
请求为
s1
预留 200 个字符的空间。然而,标准库的实现可能会分配比请求稍多的空间,以优化内存分配和管理。这是因为标准库的实现通常会根据某种内部规则进行内存分配,以减少未来可能的内存重新分配操作,所以这里得到的容量是 207,而不是 200
编译器不支持预支出内存的缩小
💕24.string类中的resize函数
resize函数用于改变string类中的size大小,改变后的size大小都会默认初始化为'\0'
代码及效果图如下->:
int main() { string s1; //s1.resize(5, '0'); s1.resize(5); s1[4] = '3'; s1[3] = '4'; s1[2] = '5'; s1[1] = '6'; s1[0] = '7'; // 76543 // 插入(空间不够会扩容) string s2("hello world"); s2.resize(20, 'x'); //20:表示要将 s2 的长度(size)调整为 20 个字符。 //'x':表示在将 s2 的长度扩展到 20 个字符时,使用字符 'x' 来填充新添加的位置。 cout << s2 << endl; // 删除 s2.resize(5); //只保留5个有效数据,其他的全部舍弃 cout << s2 << endl; }
💕25.string类中的find函数与rfind函数
rfind
是std::string
类的成员函数,用于查找某个字符最后一次出现的位置 ,它从字符串的末尾开始向前查找,与find
函数(从字符串的开头开始向后查找)相对应这两个函数的返回值都为字符的下标,并不是地址
我们来试验一下->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<algorithm>; using namespace std; int main() { string s1("hello world"); size_t pos1 = s1.find('l'); cout << pos1 << endl; size_t pos2 = s1.rfind('o'); cout << pos2 << endl; return 0; }
💕26.string类中的substr函数
substr
函数用于从string中提取子字符串。它允许你根据起始位置和子串长度来提取原字符串的一部分。
string substr (size_t pos = 0, size_t len = npos) const;
pos
:表示子串开始的位置,默认值为 0,即从字符串的开头开始提取。len
:表示要提取的子串的长度,默认值为npos
,表示提取从pos
开始到字符串的末尾。- 我们用代码来看一下效果->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> using namespace std; int main() { string s1("wo yao wan ming ri fang zhou"); size_t a = s1.find('r'); string tep = s1.substr(a); string tep1 = s1.substr(a,3); cout << tep << endl; cout << tep1 << endl; }
💕27.string中的find_first_of函数
find_first_of
函数用于在std::string
中查找字符集合中任意一个字符首次出现的位置。它会从字符串的开头开始向后查找,一旦找到字符集合中的任意一个字符,就会返回其下标
如果
std::string
的find_first_of
函数找不到所需的字符或字符集合中的任意一个字符,它将返回std::string::npos
请看代码及效果图->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> using namespace std; void test_string13() { string str("Please, replace the vowels in this sentence by asterisks."); size_t found = str.find_first_of("aeiou"); while (found != std::string::npos) { str[found] = '*'; found = str.find_first_of("aeiou", found + 1); } std::cout << str << '\n'; } int main() { test_string13(); return 0; }
💕28.string中的find_last_of函数
find_last_of与find_first_of函数只有一点不同,那就是,last是从后往前开始找,只有这一点不同
find_last_of
函数用于在std::string
中查找字符集合中任意一个字符最后一次出现的位置。它从字符串的末尾开始向前查找,一旦找到字符集合中的任意一个字符,就会返回其下标
函数找不到所需的字符或字符集合中的任意一个字符,它将返回
std::string::npos
💕29.string重载操作符+,<
string中把+重载为尾插,<重载为正常的字符比大小,这里直接上代码和效果图->:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> using namespace std; void test_string14() { string s1 = "hello"; string s2 = "hello11"; string ret1 = s1 + s2; cout << ret1 << endl; string ret2 = s1 + "xxxxx"; cout << ret2 << endl; string ret3 = "xxxxx" + s1; cout << ret3 << endl; //字典序比较 cout << (s1 < s2) << endl; } int main() { test_string14(); return 0; }