这一篇我来介绍一下C++ string类中常用好用的库函数,附上使用例子:
推荐一个查C++库函数的链接:Reference - C++ Reference (cplusplus.com) 有什么需要的函数可以自己查阅
在C语言中,我们就接触过字符串,它是以\0为结尾的一些字符的集合。但在C语言使用string系列的函数比较麻烦,稍不留神还会出错。为了方便和快捷的使用,C++中就添加了string类以及相关的库函数。
下面,我将开始介绍重点的几个字符串:
string() | 构造空的string类对象,即空字符串 |
|---|---|
string(const char* s) | 用C-string来构造string类对象 |
string(const string& s) | 拷贝构造函数 |
使用例子:
void Teststring()
{
string s1; // 构造空的string类对象s1,不需要传参。
string s2("Hello World"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}拓展:
也可以只拷贝部分字符,具体操作如下:
string s4(s2,0,5); //会输出 "Hello"
第二个参数pos很简单,就是位置的意思,第三个参数len = npos,就有意思了:

它是string类定义的一个静态对象,const size_t类型(也就是无符号整型,x86环境下是4字节,
x64环境下是8字节)其值是-1,C语言我们学过原反补的概念, -1 代表 INT_MAX
如果给的参数错误,比如远远超过了字符串的字符数量,那么len就是npos,缺省值npos,不传参数也是npos,所以:
string(s2,6,10000); //直接从W拷贝到结尾,"World"
string(s2,6); //len=npos,缺省值,直接拷贝到结尾 赋值运算符重载
s7 = "xxxx" //直接用字符串赋值如果s7是“1234567” 等,长度大于右边,也没用,直接被替换成“xxxx”

他的底层可以理解为是这样:(这里就不深究,后面将底层原理会详细展开)

得益于这个方括号重载,我们可以让字符串和数组一样进行访问,修改:
string s1("Hello World");
s1[0] = x; //索引0,也就是第1个字符改成x
cout<<s1<<endl; // "xello World"
cout<<s1[0]<<endl; // 'x' 当然也可以遍历修改全部,用for循环。
注意到C++,这个重载是传引用返回,说明修改的是s1本身,可以修改返回对象
还重载了一个const重载,因为string对象有 普通对象 和 const对象。
不可以越界修改,甚至越界访问都不行,有严格的断言检查。越界就报错,并且中止程序

这两个函数功能一样 at函数的格式是:s1.at(12) 类似这样。
唯一不同的是,at对越界检查不是那么严格,它会抛异常,而不是直接中止程序。
了解即可。
size | 返回字符串有效字符长度 |
|---|---|
empty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符(不清除空间) |
reserve | 为字符串预留空间(会增容,不一定会缩容)(下方详细) |
resize | 将有效字符的个数改成n个,多出的空间用字符c填充 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
前面5个加粗的比较重要,需要记住。 看第六个length,size和length功能和底层完全相同,名字不同。引入size()的原因是为了与其他容器的接 口保持一致,一般情况下基本都是用size()。
举例:
string s1 = "123456"
cout<<s1.length()<<endl; //6
cout<<s1.size()<<endl; //6 都不包含 \0size 结合前面的 operator[ ],可以更好地做到for循环遍历+修改
for(int i = 0;i<s1.size();i++)
{
s1[i]++;
}clear是清理数据用的,但是不清理空间 使用:s1.clear();
empty就是判空,在string基本等效于if(s1=="")... 使用:if(s1.empty()) ,empty返回类型是bool
但在其他容器,用if(s1=="")... 不适用,所以empty在其他容器还是有点用处的
reserve的作用一般是确定要插入多少个字符,提前扩容:比如我知道这个s1字符串需要插入200的字符,我可以直接插入200,减少空间的浪费 使用: s1.reserve(200) 总结就是按需分配,减少性能和空间损耗。
第七个capacity:正好可以结合reserve用来讲讲扩容缩容机制。不同的编译器扩容机制可能不一样:

reserve是预留空间的函数,如果预留的还不够本身的长度,则不会预留。若预留超过了本身长度,如“hello
world” 是11个字符(空间是15),在vs下:reserve(20)(增加到20空间)就会增加到31,进行二倍扩容。在gcc中也一样,二倍扩
容(下方代码和运行结果演示:)
注意,reserve一定会扩容,但不确定是否会缩容:

右边这串代码在vs和gcc,gcc会先二倍扩容,然后二倍缩容,vs则只是会扩容,不会缩容。具体取决于编译器
所以,不要尝试用reserve去缩容!

用法就是这样,它会截断5后面的全部数据,所以执行resize(5)后,s3就变成 “hello” 了。这是第一个版本

第二个版本,多了一个参数char c,作用如下:

第二个版本,当大于当前数据,就会把少了的数据都填充c,没有指定c就填充空字符 \0 :

“hello”会变成 “helloxxxxx”

如果再接着这样:
s3.reserve(15);
结果就看似一样,没添加,但实则是没显示:

从图中可以看出,后面5个都填充了空字符 \0
迭代器是行为像指针一样的东西
begin+end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位 置的迭代器 |
|---|
begin() 返回起始位置的迭代器 end() 返回\0位置的迭代器
这 两个函数 是和 迭代器 一起用的,begin()指向第一个字符,end()指向最后一个字符的下一个字符,也就是 \0 位置,数学上
来说:
[begin,end) 这是左闭右开的区间。

使用例子:
string::iterator it1 = s1.begin(); //指向第一个字符
while(it1 != s1.end()) //不等于\0
{
(*it1)--; //对迭代器解引用,获取指向的字符,对字符--
cout<*it1<<" "; //打印修改后的字符,并用字符串" "(一个空格)分隔。
++it1; //迭代器自增,指向下一个字符
}迭代器是访问所有容器的方式,在string中,下标加方括号的方式更方便,但是其他更繁琐的容器中,迭代器的作用就体现出来了。
举例一个链表的例子:
#include<list>
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
list<int>::iterator lit1 = lt.begin();
while(lit1 != lt.end())
{
// (*lit1)++ //加上这个就是每个的值++,打印结果是 2 3 4
cout<<*lit1<<' '; // 1 2 3
++lit1;
}迭代器的作用就体现出来了,链表不支持 下标+方括号 的访问。
迭代器也有 “const 迭代器” ,不过const不是修饰迭代器本身(不然迭代器指向改变不了,只能指向一个地方),而是修饰参数、(迭代器访问到的元素是const,不可修改值):

如图,有const_iterator 类型为了凸显const,名字加上了const。 end()同理,也有这个类型。
本质上这两个迭代器是两个类型了。 使用例子:
void Print(const string& s) //不可修改的字符串
{
string::const_iterator it1 = s.begin(); //调用const_iterator迭代器
while (it1 != s.end())
{
// *it1 = 'x'; //不可修改,因为是只读的字符串
cout << *it1 << " ";
++it1;
}
}
还有反向迭代器:const_reverse_iterator ,倒着遍历,同样还有rend()

rebegin() 返回指向的最后一个字符 rend() 返回指向的第一个字符的前一个,使用:
void Print(const string& s) //不可修改的字符串
{
string::const_reverse_iterator it2 = s.rbegin();
while (it2 != s.rend())
{
cout << *it2 << " ";
++it2;
}
}为什么是++it2? 翻转后不应该是--it2吗?
因为反向迭代器,重载了++的含义,重载成了--这也是为了更便捷的使用反向迭代器。所以可不能改成“--it2”了哦

像之前我们写的算法,比如查找函数 find,都是针对某一个具体容器的,比如针对数组,字符串,现在,借助迭代器,我们可以将这种算法泛型化:

这是 “算法” 的头文件,里面有很多函数,其中,实现了通用的函数 find:

使用模板,利用迭代器 ,传参数就传该容器的迭代器,这样实现了find函数的泛型化
图中 第四行:first != last ,为什么不用 < ?
因为迭代器不一定支持大小比较。比如链表,是双向链表,不支持迭代器比较大小,使用 < 会直接报错。!=为了提高通用性。
算法泛型化,find使用例子:

有了算法泛型化,我就可以直接使用find查找各种容器,如图。
参数不局限于begin和end,也可以修改,比如:s1.begin()+3,s1.end()-1,但需要是在迭代器的基础上合法修改。

这里就使用了类模板,传参过去。
auto是类型,它会通过初始化值的类型自动推导对象类型:
auto a = 3 //auto 是 int
auto b = 3.0 //auto 是 double这是普通类型,用auto还麻烦。但是长类型中,用auto就十分方便:
// list<int>::iterator lit1 = lt.begin();
auto lit1 = lt.begin();auto虽然好用,但一定程度降低了代码可读性。所以,请不要auto满天飞。
auto可以推导指针:
auto p1 = &s1;
auto* p2 = &s1;两种写法都一样,区别就是,第一个写法也可以传别的类型,第二个写法指定了一定是指针。这里可以类推引用。
int i = 3;
int& ref1 = i;
auto ref2 = ref1;请注意,如图这样,auto不能推导出是引用,因为ref1类型是int,所以会推导出int。需要指定& :auto& ref2 =ref1;
直接上例子,直观理解:
for(auto ch : s1) //字符串
{
cout<<ch<<' ';
}
for(auto lt : lt1) //链表
{
cout<<lt<<' ';
}范围for:自动取容器当中的数据(s1)赋值(给ch),自动迭代++,自动判断结束。 十分实用
不一定需要写auto,范围for不是必须结合auto。 可以写 char ch , int(若lt1是int链表),只是习惯了和auto一起用。
只需要注意别搞混: 参数中冒号左边的是右边的数据类型,而不是右边的类型。
举个例子:auto ch : s1 ,冒号右边是string类型,但其元素是char类型,所以auto推导为char,而不是string。
for(auto ch : s1)
{
--ch; //修改值
}
for(auto ch : s1)
{
cout<<ch<<' ';
}注意,这样是不能改变打印的值的,因为ch只是s1数据的拷贝,并不改变s1本身,第二个范围for,ch的值又被s1的数据重新覆盖了。
正确方式:
for(auto& ch : s1)
{
--ch; //此时ch是引用,所以修改的是s1的数据
}
for(const auto& ch : s1) //const auto& 更好,只读,引用减少传参
{
cout<<ch<<' '; //s1的数据已经被修改,打印的是第一个for循环的值
}支持迭代器的容器,都可以用范围for,范围for的底层其实就是迭代器。数组也可以,因为数组的行为类似于迭代器,所以被特殊处理过,也可以用范围for:

append是追加的意思,push_back是尾插,内容比较少于是合在一起讲.
push_back只能插单个字符,不能插入字符串:

append就重载了很多个版本,可以插入字符串,字符,部分字符串,多个字符等等:

不过最重要的就 1和3 ,插入字符串(1是string类字符串,3是字符数组,C格式字符串,都是字符串。)
举使用例子:


前面两个相比这个,可以说是一坨。
operato+= 简直就是 "21世纪最伟大的发明" ,这个十分好用,打死都得记住

它可以+= 字符串,还可以+= 字符,比前面两个方便,快捷:


s2分别+= 字符‘ ’ 和字符串“hello bit” ,简洁明了。

这是operator+,相比于operator+=,功能一样齐全,用处少一点点。operator+=改变自身,所以是传引用返回。operator+不修改自身,所以是传值返回。
C++把他重载成了全局函数。 这是为什么呢?我举个例子就明白了:

目的就是让第二个写法也可以运行。因为成员函数的有this指针,左操作数必须是类对象(s3),但是全局函数没这个顾虑,可以左右互换。
有人会纳闷,为什么string类没加头插,头删,因为这两个函数就可以完成该功能。不过不太常用,效率一般比较低,不推荐用。
这是insert,主要功能是在字符串的指定位置插入字符、字符串。

//在头部插入一个字符串
s3.insert(0,"hello")
//在头部插入一个字符的写法,很鸡肋。
s3.insert(0,"x")
s3.insert(0,1,'x'); //1是个数这是erase,主要功能就是删除。效率需要看删的位置,也不太常用

注意这个npos,不传,传太大了就是直接从pos位置直接删到结尾。


其中1和2是几乎一样的,就是在指定位置往后找字符串,找到后返回其索引,没找到返回npos。只是字符串格式不同,第三个十分不常用
第四个是重点,是在指定位置开始往后查找字符c,找到返回其索引,没找到返回npos(INT_MAX):
找字符g:没找到

找字符d:找到了 13

关于C++string的主要库函数就到这了,感谢支持。