C++ string类 常用的库函数 + 迭代器,范围for,auto,附使用方式

这一篇我来介绍一下C++ string类中常用好用的库函数,附上使用例子:

推荐一个查C++库函数的链接:Reference - C++ Reference (cplusplus.com) 有什么需要的函数可以自己查阅

引言:在C语言中,我们就接触过字符串,它是以\0为结尾的一些字符的集合。但在C语言使用string系列的函数比较麻烦,稍不留神还会出错。为了方便和快捷的使用,C++中就添加了string类以及相关的库函数。

下面,我将开始介绍重点的几个字符串:

1.string对象常见的构造接口string()

构造空的string类对象,即空字符串

string(const char* s)

用C-string来构造string类对象

string(const string& s)

拷贝构造函数

使用例子:

代码语言:javascript复制void Teststring()

{

string s1; // 构造空的string类对象s1,不需要传参。

string s2("Hello World"); // 用C格式字符串构造string类对象s2

string s3(s2); // 拷贝构造s3

} 拓展:

也可以只拷贝部分字符,具体操作如下:

代码语言:javascript复制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,所以:

代码语言:javascript复制string(s2,6,10000); //直接从W拷贝到结尾,"World"

string(s2,6); //len=npos,缺省值,直接拷贝到结尾 2.operator=赋值运算符重载

代码语言:javascript复制s7 = "xxxx" //直接用字符串赋值如果s7是“1234567” 等,长度大于右边,也没用,直接被替换成“xxxx”

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

得益于这个方括号重载,我们可以让字符串和数组一样进行访问,修改:

代码语言:javascript复制string s1("Hello World");

s1[0] = x; //索引0,也就是第1个字符改成x

cout<

cout<

注意到C++,这个重载是传引用返回,说明修改的是s1本身,可以修改返回对象

还重载了一个const重载,因为string对象有 普通对象 和 const对象。

不可以越界修改,甚至越界访问都不行,有严格的断言检查。越界就报错,并且中止程序

3.1 at这两个函数功能一样 at函数的格式是:s1.at(12) 类似这样。

唯一不同的是,at对越界检查不是那么严格,它会抛异常,而不是直接中止程序。

了解即可。

4.string对象常见的容量操作接口size

返回字符串有效字符长度

empty

检测字符串释放为空串,是返回true,否则返回false

clear

清空有效字符(不清除空间)

reserve

为字符串预留空间(会增容,不一定会缩容)(下方详细)

resize

将有效字符的个数改成n个,多出的空间用字符c填充

length

返回字符串有效字符长度

capacity

返回空间总大小

size和length的区别前面5个加粗的比较重要,需要记住。 看第六个length,size和length功能和底层完全相同,名字不同。引入size()的原因是为了与其他容器的接 口保持一致,一般情况下基本都是用size()。

举例:

代码语言:javascript复制string s1 = "123456"

cout<

cout<

代码语言:javascript复制for(int i = 0;i

{

s1[i]++;

}clear和emptyclear是清理数据用的,但是不清理空间 使用:s1.clear();

empty就是判空,在string基本等效于if(s1=="")... 使用:if(s1.empty()) ,empty返回类型是bool

但在其他容器,用if(s1=="")... 不适用,所以empty在其他容器还是有点用处的

reserve与扩容缩容机制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去缩容!

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

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

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

“hello”会变成 “helloxxxxx”

如果再接着这样:

代码语言:javascript复制s3.reserve(15);结果就看似一样,没添加,但实则是没显示:

从图中可以看出,后面5个都填充了空字符 \0

5.迭代器iterator迭代器是行为像指针一样的东西

begin+end

begin获取一个字符的迭代器 + end获取最后一个字符下一个位 置的迭代器

begin() 返回起始位置的迭代器 end() 返回\0位置的迭代器

这 两个函数 是和 迭代器 一起用的,begin()指向第一个字符,end()指向最后一个字符的下一个字符,也就是 \0 位置,数学上

来说:

[begin,end) 这是左闭右开的区间。

使用例子:

代码语言:javascript复制string::iterator it1 = s1.begin(); //指向第一个字符

while(it1 != s1.end()) //不等于\0

{

(*it1)--; //对迭代器解引用,获取指向的字符,对字符--

cout<*it1<<" "; //打印修改后的字符,并用字符串" "(一个空格)分隔。

++it1; //迭代器自增,指向下一个字符

}迭代器是访问所有容器的方式,在string中,下标加方括号的方式更方便,但是其他更繁琐的容器中,迭代器的作用就体现出来了。

举例一个链表的例子:

代码语言:javascript复制#include

list lt;

lt.push_back(1);

lt.push_back(2);

lt.push_back(3);

list::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()同理,也有这个类型。

本质上这两个迭代器是两个类型了。 使用例子:

代码语言:javascript复制void Print(const string& s) //不可修改的字符串

{

string::const_iterator it1 = s.begin(); //调用const_iterator迭代器

while (it1 != s.end())

{

// *it1 = 'x'; //不可修改,因为是只读的字符串

cout << *it1 << " ";

++it1;

}

}5.1反向迭代器reverse_iterator还有反向迭代器:const_reverse_iterator ,倒着遍历,同样还有rend()

rebegin() 返回指向的最后一个字符 rend() 返回指向的第一个字符的前一个,使用:

代码语言:javascript复制void Print(const string& s) //不可修改的字符串

{

string::const_reverse_iterator it2 = s.rbegin();

while (it2 != s.rend())

{

cout << *it2 << " ";

++it2;

}

}为什么是++it2? 翻转后不应该是--it2吗?

因为反向迭代器,重载了++的含义,重载成了--这也是为了更便捷的使用反向迭代器。所以可不能改成“--it2”了哦

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

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

使用模板,利用迭代器 ,传参数就传该容器的迭代器,这样实现了find函数的泛型化

图中 第四行:first != last ,为什么不用 < ?

因为迭代器不一定支持大小比较。比如链表,是双向链表,不支持迭代器比较大小,使用 < 会直接报错。!=为了提高通用性。

算法泛型化,find使用例子:

有了算法泛型化,我就可以直接使用find查找各种容器,如图。

参数不局限于begin和end,也可以修改,比如:s1.begin()+3,s1.end()-1,但需要是在迭代器的基础上合法修改。

这里就使用了类模板,传参过去。

6.范围for和auto6.1autoauto是类型,它会通过初始化值的类型自动推导对象类型:

代码语言:javascript复制auto a = 3 //auto 是 int

auto b = 3.0 //auto 是 double这是普通类型,用auto还麻烦。但是长类型中,用auto就十分方便:

代码语言:javascript复制// list::iterator lit1 = lt.begin();

auto lit1 = lt.begin();auto虽然好用,但一定程度降低了代码可读性。所以,请不要auto满天飞。

auto可以推导指针:

代码语言:javascript复制auto p1 = &s1;

auto* p2 = &s1;两种写法都一样,区别就是,第一个写法也可以传别的类型,第二个写法指定了一定是指针。这里可以类推引用。

代码语言:javascript复制int i = 3;

int& ref1 = i;

auto ref2 = ref1;请注意,如图这样,auto不能推导出是引用,因为ref1类型是int,所以会推导出int。需要指定& :auto& ref2 =ref1;

6.2范围for直接上例子,直观理解:

代码语言:javascript复制for(auto ch : s1) //字符串

{

cout<

}

for(auto lt : lt1) //链表

{

cout<

}范围for:自动取容器当中的数据(s1)赋值(给ch),自动迭代++,自动判断结束。 十分实用

不一定需要写auto,范围for不是必须结合auto。 可以写 char ch , int(若lt1是int链表),只是习惯了和auto一起用。

只需要注意别搞混: 参数中冒号左边的是右边的数据类型,而不是右边的类型。

举个例子:auto ch : s1 ,冒号右边是string类型,但其元素是char类型,所以auto推导为char,而不是string。

代码语言:javascript复制for(auto ch : s1)

{

--ch; //修改值

}

for(auto ch : s1)

{

cout<

}注意,这样是不能改变打印的值的,因为ch只是s1数据的拷贝,并不改变s1本身,第二个范围for,ch的值又被s1的数据重新覆盖了。

正确方式:

代码语言:javascript复制for(auto& ch : s1)

{

--ch; //此时ch是引用,所以修改的是s1的数据

}

for(const auto& ch : s1) //const auto& 更好,只读,引用减少传参

{

cout<

}支持迭代器的容器,都可以用范围for,范围for的底层其实就是迭代器。数组也可以,因为数组的行为类似于迭代器,所以被特殊处理过,也可以用范围for:

7.(Modifiers)修改string对象的函数7.1 push_back 和 appendappend是追加的意思,push_back是尾插,内容比较少于是合在一起讲.

push_back只能插单个字符,不能插入字符串:

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

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

举使用例子:

7.2operator+=前面两个相比这个,可以说是一坨。

operato+= 简直就是 "21世纪最伟大的发明" ,这个十分好用,打死都得记住

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

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

7.3operator+这是operator+,相比于operator+=,功能一样齐全,用处少一点点。operator+=改变自身,所以是传引用返回。operator+不修改自身,所以是传值返回。

C++把他重载成了全局函数。 这是为什么呢?我举个例子就明白了:

目的就是让第二个写法也可以运行。因为成员函数的有this指针,左操作数必须是类对象(s3),但是全局函数没这个顾虑,可以左右互换。

7.4 insert 和 erase有人会纳闷,为什么string类没加头插,头删,因为这两个函数就可以完成该功能。不过不太常用,效率一般比较低,不推荐用。

这是insert,主要功能是在字符串的指定位置插入字符、字符串。

代码语言:javascript复制//在头部插入一个字符串

s3.insert(0,"hello")

//在头部插入一个字符的写法,很鸡肋。

s3.insert(0,"x")

s3.insert(0,1,'x'); //1是个数这是erase,主要功能就是删除。效率需要看删的位置,也不太常用

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

8.find其中1和2是几乎一样的,就是在指定位置往后找字符串,找到后返回其索引,没找到返回npos。只是字符串格式不同,第三个十分不常用

第四个是重点,是在指定位置开始往后查找字符c,找到返回其索引,没找到返回npos(INT_MAX):

找字符g:没找到

找字符d:找到了 13

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

友情链接: