浅浅介绍一下string,若读者老爷有兴趣不妨垂阅!
目录
1.成员函数接口
1.1.默认成员函数相关接口
1.1.1.constructor(构造函数)
1.1.2.destructor (析构函数)
1.1.3.operator=(赋值运算符重载)
1.2.迭代器(Iterators)相关接口
1.2.1.begin
1.2.2.end
1.2.3.rbegin
1.2.4.rend
1.2.5.cbegin、cend、crbegin、crend
1.3.容量(Capacity)相关接口
1.3.1.size
1.3.2.length
1.3.3.max_size
1.3.4.resize
1.3.5.capacity
1.3.6.reserve
1.3.7.clear
1.3.8. empty
1.3.9.shrink_to_fit
1.4.访问(Element access)相关接口
1.4.1operator[]
1.4.2. at
1.5.修改(Modifiers)相关接口
1.5.1.operator+=
1.5.2.append
1.5.3.push_back
1.5.4.insert
1.5.5.erase
1.5.6.pop_back
1.6.字符串操作(String operations)相关接口
1.6.1.c_str
1.6.2.substr
1.6.3.find
1.6.4.rfind
1.6.5.find_first_of
1.6.6.find_first_not_of
2.非成员函数接口
2.1.relational operators
2.2.operator<<、operator>>
2.3.operator+
2.4.getline
3.小知识
俺上一篇博客最后浅浅介绍了一下STL,其中STL的六大组件之一就是容器。容器大致来说就是现成的数据结构,当俺们编程的时候想要使用栈、队列、二叉树……的模式来管理数据时,可以直接使用现成的STL里面的容器(栈、队列、二叉树……),就不用我们自己先手动实现栈、队列、二叉树……了。
需要明确的是:string严格来说不属于STL的一部分,属于C++标准库,因为string的诞生时间要早于STL。但是string从功能方面上来说我们可以将其归类到STL里面的容器里面。本博客就浅浅介绍一下STL的容器中的string!
string其实是由一个类模板basic_string实例化而来的,在使用string类时,必须包含头文件<string>以及使用命名空间std。俺们可以在这个文档查询头文件<string>。俺们点击这个文档映入眼帘的就是如下头文件<string>文档界面(没有截全屏):
俺们可以看到类模板实例化了四个类,分别是:string、u16string、u32string、wstring。为什么实例化了那么多类呢?这其实跟编码(Unicode)有关系了,有兴趣的读者老爷可以自行去了解。本篇博客是介绍string类的,有关string类的文档我们可以点击string类文档查阅,也可以点击上述界面的string查阅,都可以调出string类文档。若采取点击上述界面的string查询,步骤如下:
点击string过后跳转到string类文档界面(没有截全屏)如下:
那么我们查阅string类文档,首先我们就可以看到string类的来源:类模板basic_string实例化了basic_string<char>类,该类又被typedef成了string。
说了这么多,那么string到底干啥用的呢?简单点说,string类是一个管理char类型字符串的类,string类底层实现大致是一个字符顺序表!!
那么俺一边参考string类文档一遍介绍string类。由于string类接口(接口也就是string类已经实现好的成员函数或者非成员函数,我们可以直接使用的)众多,俺只是浅浅介绍一部分接口,且只介绍C++98版本的接口,没介绍的接口可以根据文档自行查阅!
1.成员函数接口
string类文档成员函数部分截图如下:
1.1.默认成员函数相关接口
1.1.1.constructor(构造函数)
PS:打开构造函数文档界面有两种办法:
1.点击俺给的链接:constructor 。
2.点击string类文档界面的constructor:
当俺们打开了构造函数文档界面后,如下图(部分截图):
该如何看着个文档呢?其实文档介绍得很清楚的。
我们看C++98的版本,构造函数重载了7个构造函数,这7个构造函数的具体介绍分别见于以下对应标号的内容,如下图:
俺介绍一些常用的构造函数:
1.string()
根据对应构造函数具体介绍:Constructs an empty string, with a length of zero characters.俺们得知这个构造函数的功能是构造空的string类对象,即空字符串。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1;cout << S1 << endl;return 0;
}
这里string类实例化对象S1并且调用了构造函数string() 。运行结果如下:
PS:string类在非成员函数接口处重载了流插入运算符(<<)和流提取运算符(>>),所以我们可以直接使用,以下不再提示!
2.string (const char* s)
根据对应构造函数具体介绍: Copies the null-terminated character sequence (C-string) pointed by s.俺们知道这个构造函数的功能是用C-string来构造string类对象。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("I love to eat Totoro");cout << S1 << endl;return 0;
}
运行结果如下:
3.string (const string& str)
根据拷贝构造函数的定义可知,这是一个拷贝构造函数,根据函数具体介绍:Constructs a copy of str.俺们知道就是用string类对象str构造一个新string类对象。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("I love to eat Totoro");string S2(S1);cout << S1 << endl << S2 << endl;return 0;
}
运行结果如下:
PS:这个拷贝构造函数完成的拷贝是深拷贝!
4.string (const string& str, size_t pos, size_t len = npos)
根据对应构造函数具体介绍:Copies the portion of str that begins at the character position pos and spans len characters (or until the end of str, if either str is too short or if len is string::npos).俺们知道用string类对象str的一部分(字符位置pos开始并跨越len字符的部分)来构造新string类对象。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("Hello Totoro");string S2(S1,6,6);cout << S2 << endl;return 0;
}
从字符位置6的字符'T'开始拷贝6个字符来构造S2,运行结果如下:
PS:字符位置pos是从0开始计算的,例如"Hello Totoro"中的字符'H'的字符位置是0、字符'T'的字符位置是6。
俺们还要注意的是:如果str太短或形参len是缺省值string::npos,则复制到str的末尾。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("Hello Totoro");string S2(S1,6,100);//从字符位置6的字符'T'开始到字符串结束最多还有6个字符,这里100>6cout << S2 << endl;return 0;
}
运行结果如下:
PS:形参len的缺省值string::npos是string类的公共静态成员变量。npos文档截屏如下:
npos的值用十进制表示是4,294,967,295。因为根据如上文档可见:-1,-1在内存中以补码的形式存在是11111111111111111111111111111111,而npos的类型是size_t的,是无符号的,所以在nops看来npos的值是整形的最大值。。
所以说当形参len是缺省值npos时,复制到str的末尾,因为这说明str太短了,str的长度是不可能为4,294,967,295的。
5.string (const char* s, size_t n)
根据对应构造函数具体介绍:Copies the first n characters from the array of characters pointed by s.俺们可知从s指向的字符数组中复制前n个字符来构造新string类对象。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("I love your Totoro", 10);cout << S1 << endl;return 0;
}
运行代码如下:
6.string (size_t n, char c)
根据对应构造函数具体介绍:Fills the string with n consecutive copies of character c.俺们知道就是用字符c的n个连续副本来构造新string类对象。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1(5, 'A');cout << S1 << endl;return 0;
}
运行结果如下:
1.1.2.destructor (析构函数)
PS:打开析构函数文档界面有两种办法:
1.点击俺给的链接:destructor。
2. 点击string类文档界面的destructor:
打开对应函数文档界面的方法以下不再赘述!!!!!
当俺们打开了析构函数文档界面后,如下图:
析构函数没什么好介绍的。string类自己实现的析构函数必然不会出现当对象销毁时申请的资源没有得到释放的问题 ,当string类对象销毁时让编译器自动调用析构函数来清理对象中的资源就好了。。
1.1.3.operator=(赋值运算符重载)
链接:operator=。
当俺们打开了赋值运算符重载文档界面后,如下图:
赋值运算符重载的功能没什么可解释的,就是为string类对象分配一个新值,替换其当前内容。我们看到赋值运算符重载重载了三个函数。简单易懂,举个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("I"), S2("LOVE"), S3("YOU");//均调用构造函数string (const char* s)S1 = "aclm";//调用string& operator= (const char* s)S2 = 'x';//调用string& operator= (char c)S3 = S1;//调用string& operator= (const string& str)cout << S1 << endl << S2 << endl << S3 << endl;return 0;
}
运行结果:
PS:
调用构造函数也可以写成这样子:
#include<string> #include<iostream> using namespace std; int main() {string S1="hello world";//调用构造函数return 0; }
1.如何区别调用的是构造函数还是赋值运算符重载呢?
我们要看是否有新string对象创建。如果有,那么调用拷贝构造函数;如果没有,那么调用赋值运算符重载!例如上述代码:主函数中第二条语句调用的就是赋值运算符重载,因为S1是已经存在的对象且没有新string对象产生。
2.以上3个赋值运算符重载均完成深拷贝!
1.2.迭代器(Iterators)相关接口
俺对迭代器也不是很熟悉,俺大概介绍一下迭代器哈。迭代器是STL六大组件之一。迭代器是用来访问STL的容器的,对所有容器都适用。
那么迭代器如何使用呢?就拿string类的迭代器来举个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1 = "Wherever you go ";string::iterator it = S1.begin();while (it != S1.end()){cout << *it;it++;}cout << endl;return 0;
}
运行结果:
浅介绍一下上面的代码:
1.不同的容器有其不同的迭代器,并且对应的迭代器属于对应容器的类域,比如上面的string类的迭代器,想要使用就必须突破string类类域,所以写成string::iterator。
2.俺用迭代器定义了一个对象it。begin()这个接口返回一个指向字符串第一个字符的迭代器。
end()这个接口返回一个指向字符串最后一个字符下一个位置的迭代器。我们可以画图理解一下:
3. 当it不等于S1.end(),就解引用并输出,再让it取下一个字符的迭代器(it++)。
4.俺们单从上面迭代器的使用来看,感觉迭代器好像指针啊。其实呢,迭代器的底层实现可能是指针,也可能不是指针。如何迭代器底层实现是指针的话,解引用和++操作那是理所当然可以的。那么当迭代器底层不是指针的话,如何能实现解引用和++操作呢?因为别忘了可以重载解引用运算符和++运算符。
俺上面使用的是正向迭代器,其实还有反向迭代器。俺还是举一个栗子看看:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1 = "Wherever you go ";string::reverse_iterator rit = S1.rbegin();while (rit != S1.rend()){cout << *rit;rit++;}cout << endl;return 0;
}
运行结果:
还是浅介绍一下代码:
1.string类的反向迭代器想要使用就必须突破string类类域,所以写成string::reverse_iterator。
2.用反向迭代器定义了一个对象rit。rbegin()这个接口返回一个指向字符串的最后一个字符的迭代器。rend()这个接口返回一个指向字符串第一个字符之前的理论元素的迭代器。我们可以画图理解一下:
3. 当rit不等于S1.rend(),就解引用并输出,再让it取 ”下“ 一个字符的迭代器(rit++)。
4.对于反向迭代器来说,若是想要取 ”下“ 一个字符(例如'h'的 ”下“ 一个字符是'W')的迭代器,就是写成rit++,可以逻辑上理解它就是反向的。所以我们可以看到结果是倒着打印S1的。
所有的容器都可以用迭代器的方式进行访问,那么当然可以用迭代器修改容器对象的内容。举个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("Vgdqdudq");string::iterator it = S1.begin();while (it != S1.end()){*it += 1;//正向迭代器修改容器对象内容it++;}cout << S1 << endl;string::reverse_iterator rit = S1.rbegin();while (rit != S1.rend()){*rit -= 1;//反向迭代器修改容器对象内容rit++;}cout << S1 << endl;return 0;
}
运行结果:
可是迭代器似乎有一个漏洞:对象有const对象,一旦初始化后就不可修改。但是似乎可以通过迭代器修改呢?其实肯定是不能的,我们可以看到:
#include<string>
#include<iostream>
using namespace std;
int main()
{const string S1("Vgdqdudq");//const对象string::iterator it = S1.begin();while (it != S1.end()){*it += 1;//正向迭代器修改容器对象内容it++;}cout << S1 << endl;string::reverse_iterator rit = S1.rbegin();while (rit != S1.rend()){cout << *rit;rit++;}return 0;
}
编译不成功的,会报错:
对于const容器对象,不论发不发生通过迭代器修改const容器对象内容的行为,使用普通迭代器都是不匹配的,编译器都会报错!!!
所以,对于const容器对象 ,我们要匹配使用const迭代器。栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{const string S1("Wherever");//const对象string::const_iterator it = S1.begin();while (it != S1.end()){cout << *it;it++;}cout << endl << endl;string::const_reverse_iterator rit = S1.rbegin();while (rit != S1.rend()){cout << *rit;rit++;}cout << endl;return 0;
}
运行结果:
所以,每种容器都有四种迭代器,分别是:普通正向迭代器(iterator)、普通反向迭代器(reverse_iterator)、const正向迭代器(const_iterator)、const反向迭代器(const_reverse_iterator)。
我们其实并不用关心迭代器底层是如何实现的,只要知道所有的容器都有其对应的迭代器,用对应的迭代器访问对应容器的方式是类似的就行。
迭代器的推出,给所有容器一种通用的访问方式。我们无需过于纠结各种容器的内部结构,都可以用迭代器这种方式访问容器,亦是封装的体现。
PS:俺在【C++】C++入门2.0中介绍过C++11中引入了基于范围的for循环。其实基于范围的for循环底层就是迭代器。意味着所有容器的访问都可以使用基于范围的for循环来完成。
讲了这么多,回归正题,俺来介绍一下迭代器的相关接口:
1.2.1.begin
链接:begin。
俺们看到这个接口重载了2个函数,分别是iterator begin()和const_iterator begin() const。根据文档介绍:Returns an iterator pointing to the first character of the string.可知返回一个指向字符串第一个字符的迭代器。
若容器对象是普通对象,会调用iterator begin()。若是const容器对象,调用const_iterator begin(() const。栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1(5, 'A');//普通容器对象const string S2("BBBBB");//const容器对象string::iterator S1it = S1.begin();//调用iterator begin()while (S1it != S1.end()){cout << *S1it;++S1it;}cout << endl;string::const_iterator S2it = S2.begin();//调用const_iterator begin()constwhile (S2it != S2.end()){cout << *S2it;++S2it;}cout << endl;return 0;
}
运行结果:
PS:从这个接口的返回值类型可知,这个接口是提供给正向迭代器使用的。这些个浅显易懂的道理,以下不再赘叙。
1.2.2.end
链接:end。
俺们看到这个接口重载了2个函数,分别是iterator end()和const_iterator end() const。根据文档介绍可知,返回最后一个字符下一个位置的迭代器,如果对象是空字符串,则此函数返回与string::begin相同的值。
同理,编译器会根据对象调用最匹配的接口,这点以下不再赘叙。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("reverehW");//普通容器对象const string S2("og uoy");//const容器对象string::iterator it1 = S1.end() - 1;//调用iterator end()while (1){cout << *it1;if (it1 == S1.begin()){break;}it1--;}cout << endl;string::const_iterator it2 = S2.end() - 1;//调用const_iterator end() constwhile (1){cout << *it2;if (it2 == S2.begin()){break;}it2--;}cout << endl;return 0;
}
运行结果:
PS:正向迭代器(包括普通迭代器和const迭代器)的迭代区间是begin()到end()之间(包括begin()和end()),如果超出这个迭代区间,就会报错。例如:
#include<string> #include<iostream> using namespace std; int main() {string S1("Wherever you go");string::iterator it = S1.begin() - 1;cout << *it;return 0; }
运行:
报错的原因就是因为it的初始值取到了S1.begin()-1,也就是第一个字符'W'之前的理论元素的迭代器,这个迭代器超出了正向迭代器规定的迭代区间。。。。
1.2.3.rbegin
链接:rbegin。
俺们看到这个接口重载了2个函数,分别是:reverse_iterator rbegin()和const_reverse_iterator rbegin() const。根据文档可知,返回一个反向迭代器,指向字符串的最后一个字符(即其反向开头)。反向迭代器向后迭代:增加它们会将它们移向字符串的开头。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("og uoy reverehW");string::reverse_iterator rit = S1.rbegin();while (rit != S1.rend()){cout << *rit;rit++;//反向迭代器向后迭代:增加它们会将它们移向字符串的开头。}cout << endl;return 0;
}
运行结果:
1.2.4.rend
链接:rend。
接口重载了2个函数,分别是:reverse_iterator rend()和const_reverse_iterator rend() const。根据文档可知,返回一个反向迭代器,指向字符串第一个字符之前的理论元素(被视为其反向端点)。请注意,即使反向迭代器增加,迭代也会在字符串中向后进行(这是反向迭代器的一个特性)。
举个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{const string S1("Wherever you go");string::const_reverse_iterator crit = S1.rend() - 1;while (1){cout << *crit;--crit;//注意反向迭代器的逻辑反向if (crit == S1.rbegin()){cout << *crit;break;}}cout << endl;return 0;
}
俺们画个图更好理解即使反向迭代器增加,迭代也会在字符串中向后进行(这是反向迭代器的一个特性)这句话,也更好理解上面代码:
运行结果:
PS:反向迭代器(包括普通迭代器和const迭代器)的迭代区间是rbegin()到rend()之间(包括rbegin()和rend()),如果超出这个迭代区间,就会报错。栗子:
#include<string> #include<iostream> using namespace std; int main() {const string S1 = "Wherever you go";string::const_reverse_iterator crit = S1.rend() - 1;while (1){cout << *crit;--crit;if (crit == S1.rbegin() - 1)//S1.rbegin()-1超出反向迭代器的迭代区间了{break;}}cout << endl;return 0; }
运行:
1.2.5.cbegin、cend、crbegin、crend
链接:cbegin、cend、crbegin、crend。
cbegin这个接口就是确定返回一个指向字符串第一个字符的const_iterator。不管string类对象是否是const对象,调用这个接口返回的迭代器均不能用来修改string类对象的内容。
cend这个接口就是确定返回一个const_iterator,指向字符串最后一个字符下一个位置。如果对象是空字符串,则此函数返回与string::cbegin相同的值。同样,调用这个接口返回的迭代器只可读不可写(就是不能修改string类对象的内容)。
crbegin这个接口确定返回一个const_reverse_iterator,指向字符串的最后一个字符(即其反向开头)。调用这个接口返回的迭代器只可读不可写。
crend这个接口确定返回一个const_reverse_iterator,指向字符串第一个字符之前的理论字符。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("Wherever you go");//普通对象string::const_iterator cit = S1.cbegin();while(cit != S1.cend()){cout << *cit;cit++;}cout << endl;const string S2 = "dereuqnoc I,was I,emac I";//const对象string::const_reverse_iterator CRIT = S2.crbegin();while (CRIT != S2.crend()){cout << *CRIT;CRIT++;}cout << endl;return 0;
}
运行结果:
1.3.容量(Capacity)相关接口
1.3.1.size
链接:size 。
函数:size_t size() const。这个接口返回字符串的长度,以字节为单位,且返回值是符合字符串内容的实际字节数,不一定等于字符串存储容量。
简单易懂,举个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world!");cout << S1.size() << endl;return 0;
}
运行结果:
PS:字符串结束标志'\0'字符不计算在内。
1.3.2.length
链接:length。
函数:size_t length() const。这个接口与string::size是一样的,返回完全相同的值。
size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world!");cout << S1.length() << endl;return 0;
}
运行结果:
1.3.3.max_size
链接:max_size。
函数:size_t max_size() const。这个接口返回字符串可以达到的最大长度。就是告诉俺们string类对象为储存字符串分配的空间最多是多少字节,这个结果是很理想化的,大概率是无法达到的,用文档原话表达:由于已知的系统或库实现限制,这是字符串可以达到的最大潜在长度,但不能保证对象能够达到该长度:在达到该长度之前,它仍然可能在任何时候无法分配存储。可知在不同平台下,得出的结果都可能不同,所以这个接口意义不大。
在俺这个平台下结果为:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1;cout << S1.max_size() << endl;return 0;
}
结果:
1.3.4.resize
链接:resize。
这个接口重载了2个函数:void resize (size_t n)和void resize (size_t n, char c)。功能都是将字符串长度(即size接口返回值)改变到n个,不同的是当字符串长度增加时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变字符串长度时,如果是字符串长度增加,可能会改变底层容量的大小;如果是将字符串长度减少,删除第n个字符以外的字符,底层空间总大小不变。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("friends are like books,you do not need a lot of them,as long as they are good!");cout << S1.size() << ' ' << S1.capacity() << endl;S1.resize(S1.size() - 72);//字符串长度减少cout << S1 << endl;cout << S1.size() << ' ' << S1.capacity() << endl << endl;string S2("hello ");cout << S2.size() << ' ' << S2.capacity() << endl;S2.resize(S2.size() + 10, 'A');//字符串长度增加cout << S2 << endl;cout << S2.size() << ' ' << S2.capacity() << endl;return 0;
}
1.3.5.capacity
链接:capacity。
函数:size_t capacity() const。这个接口返回当前为字符串分配的存储空间大小,也就是字符串容量大小,以字节表示。此返回值等于或大于字符串长度(size接口返回值),但具体是多少没有规定。
俺再次画图帮助理解,我们可以大致知道string类的底层实现就是一个字符顺序表:
俺的平台下:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("Wherever you go");cout << S1.capacity() << ' ' << S1.size() << endl;return 0;
}
运行结果:
PS:capacity这个接口的返回值并没有将存储字符'\0'的空间计算在内。
1.3.6.reserve
链接:reserve。
这个接口没有返回值。函数:void reserve (size_t n = 0)。功能是为string预留空间,不改变有效元素个数。
这个接口也就是要求为string类对象预先开好n个字节的存储字符串的空间。实际上,当n大于当前字符串容量(capacity接口返回值),也就是要求扩容时,一定会扩容,但不一定就扩容到n个字节,也可能大于n。。。当n小于字符串容量,也就是要求缩容时,就不一定会进行缩容操作了;但是如果n小于字符串长度(size接口返回值),是一定不会进行缩容的,因为不可能因为这个接口改变字符串的内容。
栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("friends are like books,you do not need a lot of them,as long as they are good");cout << S1.size() << ' ' << S1.capacity() << endl;S1.reserve(100);//扩容cout << S1.capacity() << endl;S1.reserve(S1.size() - 1);//缩容cout << S1.capacity() << endl;return 0;
}
运行结果:
PS:这个接口意义还是非常大的,可以允许我们手动开好所需空间,避免编译器自动频繁扩容,提高效率。
1.3.7.clear
链接:clear 。
该接口的函数:void clear()。擦除字符串的内容,该字符串将变为空字符串(长度(size接口的返回值)为0个字符),一般不会清理字符串容量。
栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("friends are like books,you do not need a lot of them,as long as they are good");cout << S1.size() << ' ' << S1.capacity() << endl;S1.clear();cout << S1.size() << ' ' << S1.capacity() << endl;return 0;
}
运行结果:
1.3.8. empty
链接:empty。
函数:bool empty() const。返回字符串是否为空(即其长度是否为0)。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("friends are like books,you do not need a lot of them,as long as they are good");cout << S1.empty() << endl;S1.clear();cout << S1.empty() << endl;string S2;cout << S2.empty() << endl;string S3("\0");cout << S3.empty() << endl;return 0;
}
1.3.9.shrink_to_fit
链接:shrink_to_fit。
函数:void shrink_to_fit()。这个接口的功能就是请求字符串容量减小以适应字符串长度。缩容到什么程度是不确定的,但是肯定的是这个接口肯定不会对字符串内容和字符串长度有任何影响。
#include<string>
#include<iostream>
using namespace std;
int main()
{std::string str(100, 'x');std::cout << "1. capacity of str: " << str.capacity() << '\n';str.resize(90);std::cout << "2. capacity of str: " << str.capacity() << '\n';str.shrink_to_fit();std::cout << "3. capacity of str: " << str.capacity() << '\n';return 0;
}
1.4.访问(Element access)相关接口
1.4.1operator[]
链接:operator[]。
这个接口重载了两个函数:char& operator[] (size_t pos)和const char& operator[] (size_t pos) const,分别供给普通对象和const对象使用,编译器自会调用最匹配的那个函数。这个接口返回对字符串中位置pos处的字符的引用。
PS:字符位置pos是从0开始计算的,字符串中第一个字符的字符位置pos是0。这点以下不再赘叙。
我们可以大致知道string类的底层实现就是一个字符顺序表:
比如当我们调用这个接口operator[](pos)时,返回的就是_str[pos]的引用。所以有了这个接口,string类对象像数组一样实现下标操作了。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");for (int i = 0; i < S1.size(); ++i){cout << S1[i];//隐式调用了operator[]}cout << endl;for (int i = 0; i < S1.size(); ++i){S1[i] += 3;//隐式调用了operator[]进行写操作cout << S1[i];//隐式调用了operator[]}cout << endl;return 0;
}
PS:string类对象的访问和遍历操作有三种方式:
1.使用operator[]+下标的方式,如上所示,不一定都适用于所有容器,起码string类对象是适用的。
2.使用迭代器的方式,对所有容器都适用。
3. 基于范围的for循环的方式,对所有容器都适用。
1.4.2. at
链接:at。
这个接口的作用和operator[]是一模一样的,同样重载了两个函数:char& at (size_t pos)和const char& at (size_t pos) const。不同的是,该函数自动检查pos是否是字符串中字符的有效位置(即pos是否小于字符串长度),如果不是,则抛出out_of_range异常。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");for (int i = 0; i < S1.size(); ++i){cout << S1.at(i);}cout << endl;for (int i = 0; i < S1.size(); ++i){S1.at(i) += 3;cout << S1.at(i);}cout << endl;return 0;
}
当pos超出字符串中字符的有效位置:
1.5.修改(Modifiers)相关接口
1.5.1.operator+=
链接:operator+=。
这个接口重载了三个函数:string& operator+= (const string& str)、string& operator+= (const char* s)和string& operator+= (char c)。功能就是通过在字符串的当前值末尾附加其他字符来扩展字符串。
简单易懂,举个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1 = "hello ";string S2 = "ld";S1 += "wor";//调用string& operator+= (const char* s)S1 += S2;//调用string& operator+= (const string& str)S1 += '!';//调用string& operator+= (char c)S1 += '\n';//调用string& operator+= (char c)cout << S1;return 0;
}
运行结果:
1.5.2.append
链接:append。
如文档所示,这个接口重载了很多个函数。具体函数的功能可以如图对应标号去文档查询具体功能:
俺在这里就浅浅介绍一下第三函数:string& append (const char* s)。根据文档对应的函数具体介绍:Appends a copy of the string formed by the null-terminated character sequence (C-string) pointed by s.可知功能就是在字符串后追加字符串s。
例如:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1 = "hello ";S1.append("totoro!");cout << S1 << endl;return 0;
}
运行结果:
1.5.3.push_back
链接:push_back。
函数:void push_back (char c)。将字符c附加到字符串末尾,使其长度增加1。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");S1.push_back('!');cout << S1 << endl;return 0;
}
运行结果:
1.5.4.insert
链接:insert。
在C++98版本中,这个接口重载了7个函数,都是在字符串中pos(或p)指示的字符之前插入其他字符。俺就浅介绍几个:
1.string& insert (size_t pos, const char* s)
在字符串字符位置pos处的字符前面插入字符串s。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("Totoro very delicious");S1.insert(7, "is ");cout << S1 << endl;return 0;
}
运行结果:
2.iterator insert (iterator p, char c)
在字符串中正向迭代器p指向的字符前面插入字符c。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("Totorois very deliciou");S1.insert(S1.begin() + 6, ' ');cout << S1 << endl;S1.insert(S1.end(), 's');cout << S1 << endl;return 0;
}
运行结果:
1.5.5.erase
链接:erase。
这个接口重载了3个函数。作用都是擦除字符串的一部分,缩短其长度。
1.string& erase (size_t pos = 0, size_t len = npos)
擦除字符串值中从字符位置pos开始并跨越len字符的部分,如果内容太短或len是string::npos,则擦除到字符串末尾。如果两个参数都取缺省值,擦除字符串全部字符。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("#hello# world#");cout << "完整字符串:" << S1 << endl;S1.erase(0, 1);cout << "头删后字符串:" << S1 << endl;S1.erase(5, 1);cout << "中间删后字符串:" << S1 << endl;S1.erase(S1.size()-1, 1);cout << "尾删后字符串:" << S1 << endl;S1.erase();cout << "全删后字符串:" << S1 << endl;return 0;
}
运行结果:
2.iterator erase (iterator p)
擦除正向迭代器p指向的字符。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world!");S1.erase(S1.end()-1);//尾删cout << S1 << endl;return 0;
}
运行结果:
3.iterator erase (iterator first, iterator last)
擦除正向迭代器迭代区间[first,last)中的字符序列。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("xxxxxxxAAAAA");S1.erase(S1.begin(), S1.end() - 5);cout << S1 << endl;return 0;
}
运行结果:
1.5.6.pop_back
链接:pop_back。
函数:void pop_back()。擦除字符串的最后一个字符,有效地将其长度减少一个。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world!");S1.pop_back();cout << S1 << endl;return 0;
}
运行结果:
1.6.字符串操作(String operations)相关接口
1.6.1.c_str
链接:c_str。
函数:const char* c_str() const。这个接口功能就是返回C形式的字符串,也就是返回底层的_str。如图:
这个接口的意义就是兼容C语言,很多C语言的函数接口要求传入C形式的字符串,而不是C++形式的字符串(即string类对象)。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");char str[30];memcpy(str, S1.c_str(), S1.size()+1);cout << str << endl;return 0;
}
运行结果:
PS:data 这个接口与c_str这个接口功能类似,想要具体查看可前往文档。
1.6.2.substr
链接:substr。
函数:string substr (size_t pos = 0, size_t len = npos) const。功能:在str中从pos位置开始,截取len个字符,将这些字符构造一个新的字符串对象返回。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");cout << S1.substr(0, 5) << endl;cout << S1.substr(6) << endl;return 0;
}
运行结果:
1.6.3.find
链接:find。
这个接口重载了四个函数。功能就是在字符串中搜索其参数指定的序列的第一个匹配项,找到了返回第一个匹配项的字符位置,找不到返回string::npos。
1.size_t find (const char* s, size_t pos = 0) const
在字符串的pos位置开始搜索C字符串s。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world world");cout << "first 'world' found at " << S1.find("world") << endl;return 0;
}
2.size_t find (const char* s, size_t pos, size_t n) const
在字符串的pos位置开始搜索C字符串s的一部分,这部分是由C字符串的前n个字符组成。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world world hahaha");cout << S1.find("world is so beatiful", 0, 5) << endl;cout << S1.find("x", 6, 2) << endl;return 0;
}
3.size_t find (char c, size_t pos = 0) const
在字符串的pos位置开始搜索字符c。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world!");cout << S1.find('!') << endl;cout << S1.find('a') << endl;return 0;
}
4.size_t find (const string& str, size_t pos = 0) const
在字符串的pos位置开始搜索C++字符串str(C++字符串即string类对象)。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world a world");string S2("world");size_t p = S1.find(S2);cout << "first 'world' found at " << p << endl;cout << "second 'world' found at " << S1.find(S2, p + 1) << endl;return 0;
}
1.6.4.rfind
链接:riind。
这个接口也重载了四个函数。功能跟find接口有相似之处,不同的是四个函数都是在字符串的pos位置开始“倒着”搜索,以实现在字符串中搜索由其参数指定的序列的最后一次出现。
举几个栗子,不详细介绍了,具体可查文档:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("haha.cpp.zip.c.pptx.pdf");size_t p = S1.rfind('.');cout << S1.substr(p) << endl;cout << S1.substr(S1.rfind('.', p - 1)) << endl;cout << S1.substr(S1.rfind("c", p)) << endl;return 0;
}
符合预期:
1.6.5.find_first_of
链接:find_first_of。
这个接口也是重载了四个函数。但其实跟find接口的功能也有相似之处,不同的是这四个函数在字符串的pos位置开始查找指定序列时,当与指定序列的任意一个字符匹配时就可以返回了;而find接口必须匹配整个指定序列才返回。
举个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");cout << S1.find_first_of("abcde") << endl;cout << S1.find_first_of("cccco", 5) << endl;return 0;
}
PS:
第一次接口调用:S1.find_first_of("abcde")。在C++字符串S1中的字符位置0(缺省值是0)开始查找C字符串"abcde",当匹配"abcde"的任意一个字符就返回了。 查找S1的字符位置0的字符'h',发现不匹配"abcde"的任意一个字符,那么查找S1下一个字符位置1的字符'e',发现匹配了"abcde"的字符'e',那么返回,查找结束。
第二次接口调用:S1.find_first_of("cccco",5)。在C++字符串S1中的字符位置5开始查找C字符串"cccco",当匹配"cccco"的任意一个字符就返回了。查找S1的字符位置5的字符' ',发现不匹配"cccco"的任意一个字符,那么查找S1下一个字符位置6的字符'w',发现不匹配"cccco"的任意一个字符,那么查找S1下一个字符位置7的字符'o',发现匹配了"cccco"的字符‘o’,那么返回,查找结束。
运行结果:
PS:find_last_of 这个接口功能与rfind这个接口的功能也有相似之处。相信根据find接口和find_last_of接口的关系,很容易就知道find_last_of这个接口咋用的。
1.6.6.find_first_not_of
这个接口也是重载了四个函数。但其实跟find_first_of接口的功能有相似之处,不同的是这四个函数在字符串的pos位置开始查找指定序列时,当与指定序列的全部字符都不匹配就可以返回了;而find_first_of接口是匹配指定序列的任意一个字符就可以返回了。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");cout << S1.find_first_not_of("abcde") << endl;cout << S1.find_first_not_of("cccco", 5) << endl;return 0;
}
运行结果:
PS:find_last_not_of 这个接口读者老爷应该可以猜出功能了,实在不知道就可以查文档。
2.非成员函数接口
2.1.relational operators
链接:relational operators 。
string类重载了一堆运算符重载用于比较两个C++字符串的关系或者比较一个C++字符串和一个C字符串的关系。有使用需要可以去文档查询。
举一个栗子:
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1("hello world");string S2("her");cout << (S1 > S2) << ' ' << (S1 == S2) << ' ' << (S1 < S2) << endl;return 0;
}
运行结果:
PS:流插入运算符(<<)优先级要高于大于运算符(>)等关系操作符,所以要用聚组操作符(也就是())控制执行顺序。
2.2.operator<<、operator>>
链接:operator<< 、operator>>。
像string这种自定义类型对象必须要重载流插入运算符(<<)和流提取运算符(>>)才能直接使用,额,没什么好介绍的。使用栗子上面代码大把,也不再举了,需要可以看文档。
2.3.operator+
链接:opeerator+ 。
重载了很多个函数。但是这些函数的功能都是返回一个新构造的字符串对象,这个新字符串对象是两个参数提供的字符串(或者字符)相连,但是两个参数提供的字符串(或者字符)本身是不会改变的。
#include<string>
#include<iostream>
using namespace std;
int main()
{const char* P = "hello ";string S1("totoro");char p = '!';cout << P + S1 << endl;cout << P + S1 + p << endl;cout << P << " " << S1 << " " << p << endl;//本身不变return 0;
}
运行结果:
PS:加号操作符(+)优先级高于流插入操作符(<<),所哟不用聚组操作符(就是())来干预执行顺序。
PS:本接口尽量少用,因为传值返回,导致深拷贝效率低 。
2.4.getline
链接:getline 。
这个接口重载了两个函数。
使用C++的标准输入对象(键盘)cin和C语言的函数scanf获取字符串的时候获取不到空格(' ')或换行('\n'),它们均把空格(' ')和换行('\n')作为默认分隔符号,当它们在缓冲区提取到这两个默认分隔符号之一时,就停止在缓冲区拿数据了。
而有时候C++字符串却有需求在键盘上拿获取数据,数据其中包含字符空格(' ')和字符换行('' \n),所以就可以使用这个接口。
1.istream& getline (istream& is, string& str)
第一个参数是istream对象的引用,例如cin。第二个参数是需要被写入数据的string对象。默认遇到换行('\n')才停止在缓冲区读数据。如果被写入数据的string对象原本有数据,这些数据将被替换。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1;getline(cin, S1);cout << "S1: " << S1 << endl << endl;string S2("good");getline(cin, S2);cout << "S2: " << S2 << endl;return 0;
}
运行结果:
2.istream& getline (istream& is, string& str, char delim)
这个函数与上一个函数功能类似,只不过不再以换行('\n')为默认分隔符,可以自定义分隔符delim,遇到自定义分隔符delim才停止在缓冲区读数据。
#include<string>
#include<iostream>
using namespace std;
int main()
{string S1;getline(cin, S1, '@');cout << endl << "S1: " << S1 << endl;return 0;
}
运行结果:
俺们可以打开监视窗口查看:
3.小知识
对于字符串或者数组来说, 最后一个字符下一个位置的字符位置(下标)减去第一个字符的字符位置(下标)得到的就是字符个数。
例如有一个数组"abc",最后一个字符'c'下一个位置的字符位置是3,也就是隐藏的'\0'的下标,减去第一个字符'a'的字符位置(下标)就是数组元素个数3。
感谢阅读,如有错误,欢迎斧正!