在之前的C语言学习当中我们已经了解了一系列的字符以及字符串函数,虽然这些函数也能实现对字符串进行求长度、拷贝、追加等操作,但是C语言当中的这些函数是与字符串分离的,并且最主要的是在使用这些函数时原字符串的底层空间是需要我们自己来管理的,这就很繁琐,稍不小心就会造成越界等问题。因此为了解决C语言中str系列函数的不足,在此我们就要来学习C++中提供的string类,string是用来管理字符串的类,能实现多种对字符串的操作并且在使用时不用我们显示的去扩容。在本篇中我们会来了解学习标准库中string类的各种函数以及其功能,之后会试着使用这些函数。接下来就开始本篇的学习吧!
1.string简介
其实SLT中有以下多种的容器,但是这是你可能就会疑惑这些容器当中为什么没有string呢?
Reference - C++ Reference (cplusplus.com)
其实strin严格来说不属于STL,这是因为string在STL 之前就产生了,因此就没有再将string放在容器当中,当从string的结构来看是可以将string也归为STL的
那么我们为什么要学习string呢?它的重要性如何?
其实string本质就是管理字符的数组,在此string如此重要是因为在现实当中我们许多的信息都需要使用字符串来表示,因此在掌握string的使用之后将大大提高我们代码的编写效率
在此我们使用的string其实是被typedef的模板
并且除了以上我们通常使用的string外还存在管理2字节或者4字节长度字符数组的string
当开始string等STL的学习之后我们要试着自己尝试读相应的文档,因为英文的文档才能让我们了解到第一手的资料,而如果是只通过他人翻译后就可以会有一些歧义,例如以下就是string的文档
string - C++ Reference (cplusplus.com)
在大致了解了string后接下来就来了解string类中的各个成员函数以及其使用
注:在string当中的成员函数有许多种,在以下当中有一些只需了解即可,但有一些需要我们重点的掌握,在文章以下的讲解当中有标注⭐就是要重点掌握的
2.string
2.1⭐构造函数
在string中构造函数提供了多种的接口来完成初始化的工作,在细致了解各个接口的功能前,先大致的去猜其的功能之后再去根据文档验证自己的猜想是否正确
string::string - C++ Reference (cplusplus.com)
通过文档就可以看出各个接口的功能如下所示:
string() 的功能是构造空的string类对象,即空字符串
string (const string& str) 的功能是将string类型对象str拷贝给新创建的类对象完成初始化
string (const string& str, size_t pos, size_t len = npos) 的功能是将string类型的对象str从pos位置开始拷贝len个字符给新的类对象完成初始化
注:在此的缺省参数npos表示的是字符串的最大值,由于参数len的类型是size_t也就是无符号的整型因此将其赋值为-1时表示的是一个非常大的数,因此当用户不传len的参数时就可以实现将pos下标之后的全部字符进行拷贝到新对象当中
string(const char* s) 的功能是将指针char所指向的字符串拷贝给新创建的类对象当中
string (const char* s, size_t n) 的功能是使用指针所指向的字符串中的前n个字符来完成新创建的类对象的初始化
string(size_t n, char c) 的功能是使用n的字符c来完成新创建类对象的初始化
接下来就来试着在程序当中使用string的构造函数
注:string也是在命名空间std内的,因此在使用string时可以使用展开命名空间std或者在使用string实例化出对象时声明命名空间域
使用命名空间域展开:
#include<iostream>#include<string>using namespace std;int main(){ string s1; return 0;}
声明命名空间域:
#include<iostream>#include<string>int main(){ std::string s1; return 0;}
例如以下就是使用string的各种接口来完成字符串的初始化:
#include<iostream>#include<string>using namespace std;int main(){string a;string b("Hello World!");string c(b);string d(b, 6);string e("Hello World!", 5);string f(10, 'x');cout << "a:" << a<<endl;cout << "b:" << b << endl;cout << "c:" << c << endl;cout << "d:" << d << endl;cout << "e:" << e << endl;cout << "f:" << f;return 0;}
代码输出结果如下所示:
2.2析构与赋值运算符的重载
首先是在string中析构函数会在对象销毁时自动调用,不需要我们去显示的调用
赋值运算符的重载支持以下的多个接口
string::operator= - C++ Reference (cplusplus.com)
string& operator= (const string& str) 的功能是使用一个string的对象完成新创建对象的拷贝构造
string& operator= (const char* s) 的功能是使用一个指针s指向的字符串完成新创建对象的拷贝构造
string& operator= (char c) 的功能是使用一个字符c完成新创建对象的拷贝构造
使用例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){string a;string b("Hello World!");string c(b);string d(b, 6,3);a = b;c = "XXXX";d = 'L';cout << "a:" << a<<endl;cout << "b:" << b << endl;cout << "c:" << c << endl;cout << "d:" << d << endl;return 0;}
以下代码输出结果如下所示:
2.3 容量操作
string类对象的容量操作如下所示,接下来我们就来一一介绍
string - C++ Reference (cplusplus.com)
⭐size 与capacity
string::size - C++ Reference (cplusplus.com)
string::length - C++ Reference (cplusplus.com)
在此string中的size和capacity函数返回值都是字符串有效字符长度,但在此我们更多用的是size,这是因为在string底层存储的是字符串就可以得到字符串的长度,但在STL其他的容器当中有些就没有了字符串长度这个概念,但都有元素的有效个数这个概念,因此size有着更高的通用性,之后要得到一个string对象的字符串长度通常使用的都是size
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){string a;string b("Hello World!");cout << "a.size():" << a.size() << endl;cout << "b.size():" << b.size() << endl;cout << "b.length():" << b.length() << endl;return 0;}
max_size
string::max_size - C++ Reference (cplusplus.com)
max_size是用于求string当中能开出的最大字符个数,但是这个函数在实践当中基本没有意义,这是因为在不同平台下、在不同的内存环境下能开出的最大字符个数都不同。
例如以下示例:
在VS当中在x86与x64环境下就大不相同
#include<iostream>#include<string>using namespace std;int main(){string a;cout << a.max_size();return 0;}
在x64和x86环境下输出结果依次如下所示:
⭐capacity
string::capacity - C++ Reference (cplusplus.com)
在string类中的capacity函数是用来得到类对象当中的空间大小也就是容量,这和我们之前学习的顺序表当中定义的capacity属性是相同的
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!!!!");cout << a.size()<<endl;cout << a.capacity();return 0;}
以上代码输出如下所示:
我们知道在字符串的结尾还有一个\0,因此使用对象.capacity得到的值其实是实际的空间减一的结果。就例如以上示例当对象a当中存储15个字符时,再加上\0实际的空间大小就为16,这时使用capacity得到的值为15这就验证了以上的说法
当我们再将对象a变为以下形式时,capacity会发生什么变化呢?
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!!!!!");cout << a.size()<<endl;cout << a.capacity();return 0;}
通过输出的结果就可以发现capacity扩容了
⭐ resize
https://legacy.cplusplus.com/reference/string/string/resize/
在string类中的capacity函数是用来将string类型的对象内的将有效字符的个数该成n个,多出的空间用字符c填充,但实参没有显示传c时就使用\0来填充
在使用resize时可能会出现·以下三种情况:
1.resize内参数值小于size
2.resize内参数值大于size小于capacity
3.resize内参数值大于capacity
在使用resize时在以上三种不同的情况下变化是不同的,例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!");a.resize(14);cout << a.size() << endl;cout << a.capacity()<<endl;a.resize(30);cout << a.size() << endl;cout << a.capacity()<<endl;a.resize(5);cout << a.size() << endl;cout << a.capacity() << endl;return 0;}
以上代码输出结果如下所示:
通过以上就可以发现当使用resize将string对象的有效元素个数减少时,该对象的内存大小不会缩小 ,在以上代码中其实在将三次使用resize时,对象a内部的元素变化如下所示:
⭐ reserve
string::reserve - C++ Reference (cplusplus.com)
在string中reverse的功能是为字符串预留空间,当我们一开始就确定空间的大小时就可以使用reserve来将对象内的caoacity空间改变
在一些平台下使用可以会存在内存对齐,因此使用reserve扩容的大小不一定和capacity的大小相同
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!");a.reserve(100);cout << a.size() << endl;cout << a.capacity() << endl;return 0;}
例如以上代码在VS下的输出结果就如下所示:
⭐clear
string::clear - C++ Reference (cplusplus.com)
在string中clear的功能是清空有效字符
一般在使用clear时是只会将有元素清空,而内存空间一般不会清空,在VS下是这样的。但在其他编译器下就不一定是这样的,这是因为C++标准只规定使用claer是要将有效元素清空,但没有规定是否要将空间清空
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!");a.clear();cout << a.size() << endl;cout << a.capacity() << endl;return 0;}
以上代码在VS下输出结果如下所示:
⭐empty
string::empty - C++ Reference (cplusplus.com)
在string中clear的作用是用于判断string对象的有效元素是否为空,为空时就会返回true;不为空就返回false
shrink_to_fit
string::shrink_to_fit - C++ Reference (cplusplus.com)
在string当中shrink_to_fit的作用是将string的对象的内存空间capacity缩小到和有效元素个数size一样大
2.4 访问及遍历操作
⭐下标+[ ]
string::operator[] - C++ Reference (cplusplus.com)
在string当中提供了operator[ ]运算符重载来实现下标的访问,有了该函数我们就可以通过元素的下标来查找元素或者改变对应元素的的值
例以下示例:
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!");for (int i = 0; i < a.size(); i++){ //a.operator[](i);cout<<a[i]<<" ";}cout << endl;for (int i=0; i < a.size(); i++){ //a.operator[](i)++;a[i]++;i++;}cout << endl;for (int i = 0; i < a.size(); i++){cout << a[i] << " ";}return 0;}
以上代码输出结果如下所示:
⭐迭代器
⭐正向迭代器
在访问string对象的下标时,除了可以用以上下标+[ ]的方式来实现访问外,还可以使用迭代器来实现访问,在string当中由于底层是数组才重载了operator[ ],但是之后当我们学习到链表或者是二叉树等数据结构时,由于这些结构各个元素在物理结构上是不一定连续的,这就无法使用下标+[ ]来实现遍历了,在此就要用到迭代器了。那么在使用迭代器之前可以认为迭代器是像指针一样的东西,以下我们先来了解迭代器的使用,之后再模拟实现string再来细致的讲解其是如何实现的
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!");string::iterator st1 = a.begin();while (st1!=a.end() ){cout << *st1<<" ";st1++;}return 0;}
以上代码就是使用迭代器来实现字符串每个元素的遍历,在此我们创建了一个iterator类型的变量st1,在此要注意iterator是存在string类域内的,因此在使用时要加上::。再通过文档就可以发现函数begin和end返回类型都是iterator
在以上的代码当中begin和end一开始的指向如下所示:
以上除了可以使用迭代器来实现字符串的遍历外,还可以使用迭代器来修改字符串内的元素
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!");string::iterator st1 = a.begin();while (st1 != a.end()){ (*st1)++;st1++;}st1 = a.begin();while (st1 != a.end()){cout << *st1 << " ";st1++;}return 0;}
当string对象是使用const修饰的,这时就需要使用const的迭代器
#include<iostream>#include<string>using namespace std;int main(){const string b("Hello");string::const_iterator st2 = b.begin();while (st2 != b.end()){cout << *st2 << " ";st2++;}return 0;}
注:在此由于const修饰的变量是不能修改的,因此在不能使用迭代器对对象内的元素进行修改
⭐反向迭代器
当我们要从末位置开始遍历原string的对象时以上的普通迭代器就无法满足我们的要求了,这时就需要用到反向迭代器
以上的示例使用反向迭代器就如下所示:
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World!");string::reverse_iterator st= a.rbegin();while (st != a.rend()){cout << *st << " ";st++;} cout << endl; st = a.rbegin(); while (st != a.rend()) { (*st)++; st++; } st = a.rbegin(); while (st != a.rend()) { cout << *st << " "; st++; }return 0;}
通过以下的文档可以看出在此使用到的rbegin和rend的函数返回值都是reverse_iterator,因此以上我们创建的变量st类型也是reverse_iterator
在反向遍历string对象时当string对象是使用const修饰的,这时就需要使用const的反向迭代器
#include<iostream>#include<string>using namespace std;int main(){const string b("Hello");string::const_reverse_iterator st2 = b.rbegin();while (st2 != b.rend()){cout << *st2 << " ";st2++;}return 0;}
⭐范围for
在了解范围for之前我们首先要来了解在C++11中提供的auto,auto的作用是能自动的推导变量的类型
例如以下示例:
#include<iostream>using namespace std;int main(){int i = 5;double j=6;//自动推导类型auto a = i;//intauto b = j;//doubleauto c = &i;//int*return 0;}
注意:在使用auto给变量进行定义时,必须要初始化否则会出现错误
那么auto在我们的使用当中有什么作用呢?
首先就时在我们之前迭代器的使用当中,在const修饰的反向迭代器当中,类型比较长写的时候就较为繁琐,那么有了auto就可以大大简化代码
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){const string b("Hello");auto st2= b.rbegin();//string::const_reverse_iterator st2 = b.rbegin();while (st2 != b.rend()){cout << *st2 << " ";st2++;}return 0;}
auto这种使用叫做语法糖,这种就是没有创建新的语法,而是使用一种方法来替代旧的方式,用起来更加的方便
在此在了解了auto就可以来了解范围for了,之前我们遍历的方式学习了下标+[ ]和迭代器,接下来要学习的范围for在遍历时就更加简洁
例如以下的string对象要遍历每个元素就可以使用范围for:
#include<iostream>#include<string>using namespace std;int main(){const string b("Hello");for (auto i: b){cout << i<<" ";}return 0;}
在此以上的形式就是范围for,范围for能自动++;自动判断结束,自动取容器的数据赋值给变量左边的对象
其实在C++当中范围for的底层就是通过调用迭代器来实现的,例如以上代码:
当我们要使用范围for来修改string对象内的值时,这时的范围for就与以上的形式有所不同了,这时需要在auto之后加上&让其变为引用而不再是拷贝
#include<iostream>#include<string>using namespace std;int main(){string b("Hello");for (auto& i : b){i++;}for (auto i: b){cout << i<<" ";}return 0;}
2.5 修改操作
push_back
string::push_back - C++ Reference (cplusplus.com)
在string当中提供了push_back来实现尾插,当push_back只能实现一个字符的尾插
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){ string b("Hello");b.push_back(' ');b.push_back('a');b.push_back('b');b.push_back('c'); cout<<b;return 0;}
以上代码输出结构如下所示:
append
string::append - C++ Reference (cplusplus.com)
在string还提供了append来实现字符串尾插到原字符串当中,并且支持以上的多个接口,在此这些接口和我们之前学习的构造函数的接口类似
#include<iostream>#include<string>using namespace std;int main(){ string b("Hello"); string c("World"); b.append("anc");b.append(c, 2, 3);b.append(4, 'q');cout << b;return 0;}
以上代码输出结构如下所示:
insert
string::insert - C++ Reference (cplusplus.com)
注:insert在使用时当要在原字符串当中插入字符或者字符串时,这时使用insert就会使得原字符串在要插入位置之后的字符都要向后移动,这时效率就会很低,因此insert在实践当中不可多用
在string当中还提供了insert来实现任意位置的插入,在此的insert也支持多个接口
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){ string b("Hello");string c("World");b.insert(1, "xxx");b.insert(0, c);b.insert(2, 1, 'n');cout << b;return 0;}
以上代码输出结构如下所示:
erase
string::erase - C++ Reference (cplusplus.com)
注:erase在使用时当要删除原字符串当中的字符时,这时使用rease就会使得原字符串在要删除位置之后的字符都要向前移动,这时效率就会很低,因此rease在实践当中不可多用
在string当中还提供了erase来实现任意位置的删除,在此的erase也支持多个接口
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){ string b("Hello");b.erase(2, 1);b.erase(b.begin()); cout<<b;return 0;}
以上代码输出结构如下所示:
⭐ operator+=
string::operator+= - C++ Reference (cplusplus.com)
在此在string当中还提供了+=的运算符重载,+=相比之前的push_back和append就简洁很多了,因此在以后string尾插字符或者是字符串时我们更多的是使用+=
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){ string b("Hello");string c("World");b += '!';b += "!!!"; cout << b;return 0;}
以上代码输出结构如下所示:
replace
在string当中提供了replace来实现对string对象内一段字符的替代
注:replace在使用时当原字符串当中要被替代的字符个数不与替代之后的字符数相等时,这时使用replace就会使得原字符串的底层出现大量的字符移动,这时效率就会很低,因此replace在实践当中不可多用
例如以下示例:
clude<iostream>#include<string>using namespace std;int main(){ string b("Hello");string c("World");b.replace(0, 2, "AAA");b.replace(1, 3, " World", 1, 5);b.replace(b.begin(), b.begin() + 1, "Yes");cout << b;return 0;}
以上代码输出结构如下所示:
⭐find
string::find - C++ Reference (cplusplus.com)
在string提供了find来实现从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置,当查找的字符或者字符串在string对象当中不存在时会返回string::npos
例如以下示例:
要将string对象中的空格字符都替换成%%,这时就可以使用find来查找空格字符,之后再创建一个新的string对象来存储改变之后的字符,之后再将该对象赋值给原对象就可以将原字符串当中的空格字符都替换为%%
#include<iostream>#include<string>using namespace std;int main(){string a("Hello World ");string b;//创建新string类型对象size_t i = a.find(' ');for(auto i:a)//使用范围for遍历a{if (i != ' '){b += i;}else{b += "%%";}} a=b;cout<<a;return 0;}
以上代码输出结构如下所示:
⭐rfind
string::rfind - C++ Reference (cplusplus.com)
在string提供了find来实现从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置,当查找的字符或者字符串在string对象当中不存在时会返回string::npos
find_first_of
string::find_first_of - C++ Reference (cplusplus.com)
在string提供了find_first_of来实现从字符串pos位置开始往后找含有字符串内任意字符的位置,返回该字符在字符串中的位置,当查找的字符或者字符串在string对象当中不存在时会返回string::npos
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){ string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_of("aeiou");while (found != string::npos){str[found] = '*';found = str.find_first_of("aeiou", found + 1);}cout << str << '\n';return 0;}
在以上代码中就是使用find_first_of将string对象当中的从前往后所有位置的aeiou字符都替换为字符*
以上代码输出结果如下所示:
find_last_of
string::find_last_of - C++ Reference (cplusplus.com)
在string提供了find_last_of来实现从字符串pos位置开始往前找含有字符串内任意字符的位置,返回该字符在字符串中的位置,当查找的字符或者字符串在string对象当中不存在时会返回string::npos
例如以下示例:
nclude<iostream>#include<string>using namespace std;int main(){ string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_last_of("aeiou");while (found != string::npos){str[found] = '*';found = str.find_last_of("aeiou", found + 1);}cout << str << '\n';return 0;}
在以上代码中就是使用find_first_of将string对象当中的从后往前所有位置的aeiou字符都替换为字符*
substr
string::substr - C++ Reference (cplusplus.com)
在string中提供了substr来实现在str中从pos位置开始,截取n个字符,然后将其返回
例如以下示例:
我们要实现一段代码将一个文件名的最后的后缀取出来,在此就存在一个问题了一个文件名可能会有多个后缀比如test.cpp、test.cpp.exe等,那么我们就不能从文件名的字符串第一个.开始取字符,而是要从字符串的末尾开始找.直到找到为止,从该位置之后的字符就是该文件名的最后一个后缀
#include<iostream>#include<string>using namespace std;int main(){string str("test.cpp.exe");size_t found = str.rfind('.');if (found != string::npos){string tmp = str.substr(found);cout << tmp ;}return 0;}
以上代码输出如下所示:
c_str
string::c_str - C++ Reference (cplusplus.com)
在string中提供了c_str来返回string对象底层的字符串,在此在string提供这个函数是因为在编写程序有时候是同时使用C和C++混合编程的。就例如在编写C++的代码有时候我们会调用C语言的库,这时因为C++是兼容C语言的,这就使得C++不是完全面向对象,而是即面向对象也面向过程
就例如在使用C++编写的程序当中要调用C语言的库来打开一个string类型的文件,这时就存在问题了,问题是在使用fopen时函数第一参数需要是char* 的指针而我们这时候参数使用类string对象就无法编译通过
在此就需要使用到c_str使string对象的底层字符串指针作为fopen的第一个参数
#define _CRT_SECURE_NO_WARNINGS#include<iostream>#include<string>int main(){string str("test.cpp");FILE* p = fopen(str.c_str(), "r");char ch = fgetc(p);while (ch != EOF){cout << ch;ch = fgetc(p);}return 0; fclose(p);}
2.6非成员函数
operator+
operator+ (string) - C++ Reference (cplusplus.com)
在string中提供了operator+来实现两个字符串的相加,在此将该函数设置为非成员函数是为了在string对象在使用该操作符时对象不一定要在操作符的左边
注:在此的operator+在实践当中尽量少用,这时因为opreator+是类类型传值返回,会调用拷贝构造,在此导致深拷贝效率低
例如以下示例:
#include<iostream>#include<string>using namespace std;int main(){string a("World");string b;b = "Hello " + a;cout << b;return 0;}
以上代码输出结果如下所示:
⭐operator>>
operator>> (string) - C++ Reference (cplusplus.com)
在string当中实现string对象的流插入
⭐operator<<
在string当中实现string对象的流输出
operator<< (string) - C++ Reference (cplusplus.com)
⭐relational operators
relational operators (string) - C++ Reference (cplusplus.com)
在string当中提供了以上多个比较大小的运算符重载函数
⭐getline
在string中提供了getline这个流输入函数来实现获取一行字符串或者是获取任意字符之前的字符串,在此提供该函数是因为在一些地方之前我们学习过的cin无法满足我们的要求,这是因为用户输入的信息都先是存放在缓冲区的,在之后再从缓冲区读取数据,但cin每次用到空白字符也就是空格或者是换行就停止了,但当我们要将一段包含空白字符的字符串读取输入到string对象或者是数组时cin就无法实现了,这时就要用到getline了
在此getline函数的第一个参数是istream类型的对象,在我们的日常使用当中就是cin,第二个参数是要将信息输入的对象,第三个参数是读取缓冲区数据的停止符号,当用户未输入时默认是换行符
例如以下示例就需要用到getline:
字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)
在以上的算法题当中要输出最后一个单词的长度也就是要输出最后一个空格之后的字符串,这时你可能就会直接想到先创建一个string的对象,之后再使用rfind查找字符' ',最后再输出size减rfind位置的值就可以解决以上的算法题了,你大体的想法是没问题的,但是按照以上想法该算法题无法提供是为什么呢
在此就是典型的cin无法实现我们的要求的情况,就例如以上示例使用cin就只能先将ABSIB输入到对p内,这使得对象p内字符串变为ABSIB,之后再将T输入对象p内,这使得对象p内字符串变为T,这样两次的输入就不是我们一开始想要的。因此在此要使用getline才能解决以上的算法题
#include <iostream>using namespace std;int main() { string p; while (getline(cin,p)) { size_t fount=p.rfind(' '); cout<<p.size()-fount-1; }}// 64 位输出请用 printf("%lld")
以上就是string各个函数的介绍以及使用方法讲解,接下来我们将在下一篇当中带来string的模拟实现,相信通过自主实现string你将对string有更深的理解,未完待续……