这一篇我来介绍一下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.push_back(1); lt.push_back(2); lt.push_back(3); list 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 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的主要库函数就到这了,感谢支持。