【C++】STL简介 及 string的使用
文章目录
- 1. STL简介
- 1.1 什么是STL
- 1.2 STL的版本
- 1.3 STL的六大组件
- 2. string类的使用
- 2.1 C语言中的字符串
- 2.2 标准库中的string类
- 2.3 string类的常用接口说明
- 1. string类对象的常见构造
- 2. string类对象的容量操作
- 3. string类对象的修改操作
- 4. `resize`和`reserve`
- 5. 认识迭代器(正向)
- 6. 反向迭代器
- 7. const迭代器(正向&反向)
- 8. 元素访问
- 9. insert和erase
- 10. replace、find、rfind、substr
- 11. swap与string::swap
- 12. c_str
- 13. getline
- 2.4 总结
1. STL简介
1.1 什么是STL
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
1.2 STL的版本
原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。
HP 版本——所有STL实现版本的始祖。
P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。
我们后面学习STL要阅读部分源代码,主要参考的就是这个版本。
1.3 STL的六大组件

这个大家先了解一下,我们后面都会慢慢的进行学习。
2. string类的使用
2.1 C语言中的字符串
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
2.2 标准库中的string类
那标准库中的string到底是个啥呢?
🆗,它其实是一个类模板实例化出来的一个模板类。
string类的文档介绍
我们可以看到,它其实是basic_string这个类模板实例化出来的类的一个typedef。
ps:这个页面翻译有些地方可能不恰当。
可以看到,basic_string实例化出来的模板类除了string还有三个。
它们都是basic_string这个类模板实例化出来的模板类,区别在于它们对应的模板参数的类型不同。
那对于这个string类呢?
其实它的底层就是一个动态的字符数组,就像我们之前数据结构写的顺序表。
那string呢就是一个char类型的字符数组,wstring就是对应的wchar_t的字符数组
u16string就是char16_t的字符数组,u32string就是char32_t的字符数组。
那这些不同类型的字符对应的大小也是不同的。
欸!那大家现在有没有一个疑问,为什么搞出这么多种的string类呢?
🆗,那我们在C语言阶段有了解过ASCII编码:
这里面的所有符号和字母都一个对应的ASCII码值。
那问一下大家假如现在我们要存一个字符串
char str[] = "hello";
那它在内存中存的是啥?
我们看到内存里存的并不是字母本身,而是它们对应的ASCII码值(这里以16进制显示)。
那我们去打印的时候呢其实它也是去对照这个表找到这个ASCII码对应的字母然后显示。
所以呢
ASCII其实主要是来显示英语这些语言的。
那这样的话,随着计算机的发展,只有一个ASCII编码还够用吗?
是不是就不行了啊,因为世界上还有很多国家,很多种语言呢。比如现在我们要让计算机能显示中文,用ASCII码是不是就不行了啊。而且ASCII只定义了128个字符(一个字节就够用了),中国的汉字大约有10万个呢!
那基于这样的原因呢,有人就又发明了Unicode——万国码(兼容ASCII):
但是呢各个国家的情况也不同,有的国家文字少,有的多,所以Unicode又进行了划分,分为UTF-8、UTF-16、UTF-32这些。
所以呢,为了应对这些不同的编码,就产生了这些不同的字符类型,所以就有了basic_string这个泛型字符串类模板,我们可以用它实例化出不同类型的字符串类。
🆗,那这里面最常用的呢其实还是string。
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名,
typedef basic_string<char, char_traits, allocator> string;- 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std
2.3 string类的常用接口说明
1. string类对象的常见构造

| (constructor)函数名称 | 功能说明 |
|---|---|
| string() 空字符串构造函数(默认构造函数) | 构造一个空字符串,长度为零个字符 |
string (const char* s) | 用一个常量字符串来构造字符串类对象 |
| string (const string& str, size_t pos, size_t len = npos) (用的不多) | 复制 str 中从字符位置 pos 开始并跨越 len 字符的部分(如果 str 太短或 len 是string::npos,则直到 str 的末尾) |
| string (const char* s, size_t n) | 拿s指向字符串的前n个字符去构造string对象 |
| string (size_t n, char c) | 拿n个字符c去构造string对象 |
| string (const string& str) | 拷贝构造 |
template <class InputIterator>string (InputIterator first, InputIterator last) | 涉及到迭代器,后面再说 |
先来看string():
构造一个空字符串。
string (const char* s) :
另外呢,这里还支持这样写:
那这个我们之前是不是讲过啊,单参数的构造函数是支持隐式类型转换的。
string (const string& str, size_t pos, size_t len = npos):
这个怎么用呢?
它其实是拿str中的一个子串去去构造string对象,这个字串是从str中下标pos位置开始,长度为len的一个字串。
那这个地方还说了,如果这里的str比较短,或者这里给的len是string::npos,则这个字串一直到str的末尾。
什么意思呢?
举个栗子:
我们现在的len是50,那这时字符串的长度是不是不够啊,比50短,那这个时候怎么办,会报错了?
不会的,这里它会取到字符串的结尾位置:
那我们看到这里还说如果给的len是string::npos,也会一直到str末尾,而且我们发现:
这里的参数len给的是有缺省值的,而这个缺省值就是npos,那这个npos是个啥呢?
我们看到它是一个静态成员变量,值是-1,但是呢,因为这里它的类型是size_t(无符号整型),所以它在这里其实是整型的最大值:
而我们的字符串长度是不可能大于这个值的,所以这里也是会取到结尾。
这个其实用的不是很多,但这里第一次见,带大家了解一下。
string (const char* s, size_t n):
拿s指向字符串的前n个字符去构造string对象
string (size_t n, char c):
拿n个字符c去构造string对象
string (const string& str):
拷贝构造:
2. string类对象的容量操作

总共呢有这么多。
首先我们看到有个size,还有个length:
都是返回字符串长度。
欸!那他们俩的功能一样,为什么要搞两个呢?搞一个size,搞一个length。
🆗,那这里呢其实跟一些历史原因有关,string呢其实出现的比STL早,string其实严格来说是不属于STL的,它是C++标准库产生的,在STL出现之前就已经在标准库出现了。
那string呢其实最早之前设计的就是length,因为字符串的长度嘛,用length就很合适。但是后面STL出现之后,里面的其它数据结构用的都是size,那为了保持一致,就给string也增加了一个size。
所以size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一
致,一般情况下基本都是用size()。
然后我们看到有一个max_size:
它的作用呢是返回字符串的最大长度
但是呢,真正在实际中字符串可以并不能开这么长,而且在不同平台下这个值也可能不一样。
所以这个东西大家了解一下,知道有这么个东西就行了。
然后我们来看一下capacity:
capacity呢其实就是返回当前string对象的容量(即当前给它分配的空间有多大),我们之前学过数据结构,相信这个大家很好理解。
我们看到这里返回的s的容量是15,但是呢这里想告诉大家VS下面这里它是不包含给'\0'的空间的,因为它认为'\0'不是有效字符,所以这里实际上是16个字节的空间。
我们可以调式观察一下:
然后其它的呢大家可以自己结合文档先了解一下,我们后面再详细一点去解释剩下的一些比较重要的。
3. string类对象的修改操作

ps:有的我们放在比较后面一点讲,还有的不重要的我们就简单了解一下。
先来看一下p ush_back:
顾名思义
p ush_back就是尾插嘛。
演示一下:
那这是尾插或者说追加一个字符,那要是想追加一个字符串呢?
也是可以的,不过这里不再用push_back,提供了另一个接口——append
然后这个append它也是重载了一大堆的版本,但有的其实都不怎么用,所以string的设计其实是被吐槽过的,有些地方设计的不是很好。
那最常用的呢其实还是直接去追加一个字符串:
但是呢:
其实平常我们并不喜欢用
push_back和append。
而是去用:
🆗,string还重载了+=,用起来就非常爽:
+=字符,字符串都可以
不过其实+=的底层也是用的push_back和append,对他们进行了一层封装。
4. resize和reserve
那现在我们再回过头来看一下容量中的resize和reserve:
首先我们来观察一个东西,就是我们定义一个string对象,我们观察一下在不断插入数据的过程中它是如何进行扩容的。
那这里已经写好了一个程序:
int main()
{string s;size_t sz = s.capacity();cout << "making s grow:\n";cout << "capacity changed: " << sz << '\n';for (int i = 0; i < 100; ++i){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed: " << sz << '\n';}}return 0;
}
我们运行一下:
我们看到是这样一个情况。
首先我们上面说过了嘛,他这里没有算\0的空间,所以这里看到的是15,实际是16个空间,31实际是32 好吧。
那这样的话我们看到它好像第一次扩容是2倍扩,后面每次都差不多是一个1.5倍扩。但实际呢,想告诉大家,其实在VS上,它这个结构跟我们理解的顺序表还是有一点不同,其实刚开始的数据并没有存到动态开辟的数组上,存到了一个自己的数组里面。我们可以调式观察一下:
我们看到,它是存到这个Buf数组里面了,这个数组的大小是16(不带\0就是15),所以如果string对象的大小16,就存到这个Buf数组上,大于16才存到Ptr指向的动态开辟的数组上,就不往Buf里面存了。
可以认为它的结构是一个类似这样的:
所以如果Buf 满了它第一次在堆上开空间就开32 字节,然后后面是1.5倍增长扩容,所以不能说第一次扩了2倍。
那我们可以验证一下:
我们打印它的大小发现是28个字节,如果只有指针ptr,size和capacity的话应该是12字节,那现在是28,就是因为它这里还有一个大小16的数组,那这样做的话小块的空间就可以不去堆上开辟了,如果比较大,需要去堆上开辟的话,起始就是32字节,然后不够再扩。
再来看:
现在i为100的时候,我们看到这时的字符串就存到Ptr指向的动态开辟的空间上了,就不再用Buf存了。
所以我们可以认为string的扩容是1.5倍去扩的,那在所有地方都是这样吗?
不是的,在我们目前的Vs上是这样,不同平台的实现可能就不一样的。
我们可以看一下在Linux的g++下:
是2倍扩容的,而且这样看的话它是没有Buf数组的。
那这里了解了这个扩容的机制之后:
我们真正想给大家讲的一个东西是什么呢?
🆗,其实是想给大家说一下这个reserve和resize。
那上面我们看到如果我们一直插入数据他是会去不断扩容的,那其实我们是有方法去减少扩容的。
如果我们知道要插入多少数据的话,我们可以去调这样一个接口——reserve,注意不是逆置reverse
那reserve的作用是什么呢?
reserve可以帮助我们更改容量大小,这样如果我们知道需要多大的空间,就可以一次开到位,就不用再一次一次的扩容了。
就拿我们上面那个例子来说:
我们现在直接reserve100个容量,但是注意,我们指定100,它不一定开的就是100,可能由于对齐啊等等的一些原因,它会给你多开一些空间,但是肯定不会比100小。
这次大家看还有没有扩容啊,是不是就没有了,这里直接开了111,比100多了一些。
Linux下呢:
我们看到就是给了100,这就是它们底层实现的机制可能不一样,就有一些差异。
所以呢:
如果我们知道需要多少空间的前提下,reserve就可以帮助我们提前把空间开好,然后就可以减少扩容,提升效率,因为频繁扩容也是需要付出代价的。
那还有一个resize,它的作用是什么呢?
我们说
reserve可以去改变容量,帮我们开空间;那resize呢,不仅可以开空间,而且还能对开好的空间进行初始化。
另外大家要知道reserve只是开空间改变容量,它是不会改变size的:
而resize呢:
我们看到capacity和size都变了,因为它是会对开好的空间进行初始化的,相当于插入了新字符,所以size也变了:
这里我们没有指定第二个参数,既要填入的字符,默认给的是\0,当然我们也可以自己指定要填入的字符:
当然我们刚才传的第一个参数n是大于当前字符串长度的,那么他就去扩容,如果我们传的n小于当前字符串长度,它还可以帮我们删除多出来的内容:
那大家思考一下,这样做的话,会改变capacity吗?
我们看到只是size变了,capacity并没有改变。
因为一般情况下是不会轻易缩容的,缩容的话一般是不支持原地缩的,我们之前学习realloc扩容有原地扩和异地扩两种方式,而且原地扩也是有条件的,后面要有足够的空间才能原地扩。
而缩容呢?可以原地缩吗?
🆗,由于底层内存管理的一些原因,是没法原地缩的。
如果支持原地缩,是不是就要支持释放一部分,我们申请一块空间,不用了只释放其中的一部分。
但是是不支持只释放一部分的,就像我们free是不是要求传的指针必须是指向其实位置的。
所以如果真的要缩容的话,只能异地缩,就是开一块新的小空间,把需要的数据拷贝过去,然后把原空间释放掉。所以缩容是要付出性能的代价的,系统原生是不支持的,我们需要自己去搞。所以不到万不得已不要轻易缩容。
不过其实string是提供了一个可以缩容的接口的——shrink_to_fit
但是它是要付出代价的,所以我们要谨慎使用。
5. 认识迭代器(正向)
那现在大家思考一个问题,如果我们想遍历一个string对象,可以有哪些方式?
首先可以循环用[ ]遍历,因为string是重载了[ ]的,或者我也可以用范围for。
那除了这些方法之外呢,我们还可以用迭代器。
举个栗子:
int main()
{string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}return 0;
}
解释一下:首先这里的it就是我们定义的一个string类的迭代器(string::iterator是类型),那这么理解迭代器这个东西呢?
🆗,现阶段呢,大家可以认为它是一个像指针一样的东西(不一定是指针)。
那这里的begin呢,会返回指向字符串第一个字符的迭代器。
end返回指向最后一个字符后面位置的迭代器。
我们就可以理解成指向这个位置的指针:
那这样我们去循环遍历,解引用it,就可以遍历到整个string对象。
那这样对比一下的话是不是用范围for会比较爽一些:
🆗,那这里想告诉大家的是范围for看起来好像很牛逼,但是其实它的底层也是用的迭代器。
6. 反向迭代器
那迭代器除了像上面那样支持正向从前向后遍历,其实还可以倒着遍历,倒着遍历的叫做反向迭代器。
我们看到除了begin和end这里还有rbegin和rend,它们返回的是reverse iterator即反向迭代器。
那rbegin和rend返回的是什么呢?
string s1("hello world");那还拿这个对象举例子,
大家就可以理解为rbegin是指向字符d的(但实际实现不一定是这样),rend是指向字符h的前一个
那我们来用一下:
那大家先思考一下,这个地方rit应该++还是- -?
🆗,还是++,大家可能认为这里从后往前倒着走应该是- -了。不要这样理解。
大家想,正向迭代器++是往后走,那反向迭代器就是方向相反了,那++不就是往前走了嘛。
我们验证一下:
是不是就反向遍历了。
7. const迭代器(正向&反向)
那大家再来看这样一个场景:
我们把s1传给一个函数,然后在函数里面用迭代器遍历打印它,但是这里报错了:
说不存在什么到什么的适当转换。
为什么呢?
我们看到函数func的形参s是s1的引用,但是加了const修饰,也就是说,与我们上面写的代码的区别在于这里的string对象即func中的s是const对象。
那s是const对象为什么这里就不行了呢?
🆗,const对象是不是就不能被修改了啊,那我们上面讲普通迭代器的时候说了,可以认为它是一个像指针一样的东西,那我们对它解引用是不是就可以修改它了,所以这里我们就不能用普通迭代器了,这样是不是就权限放大了,所以这里才报错了。
那怎么解决呢?
🆗,我们看到begin是有两个版本的,如果是const对象调用begin,那么返回的是const迭代器const_iterator
普通迭代器可以读容器的数据,也可以去修改,但是const迭代器就只能读,不能修改。
所以这里s调用begin返回的是const迭代器,我们用const迭代器迭代器接收就行了。
当然const迭代器我们是不能去修改的(不能修改它指向的内容,其本身可以修改)。
那同样的道理:
普通的迭代器有正向和反向,那const迭代器就也有正向和反向的两个版本。
刚才我们上面的就是正向,即const对象调用begin和end返回的迭代器。
那const反向迭代器就是const对象调用rbegin和rend返回的迭代器——const_reverse_iterator
我们来试一下:
当然这里我们看到迭代器的类型是不是有点长啊,那我们可不可以简化一下呢?
是不是可以用auto啊:
我们说auto是不是可以自动推导类型啊,但是如果你明确知道这里返回的是什么类型,写成auto可以简便一点,那如果给不知道的人看可能会有点懵。
所以说它并不是一个完全完美的东西。
那还要给大家提一下的就是:
我们看到这里C++11又提供了一套迭代器:cbegin cend crbegin crend,它们只返回const迭代器。
为什么搞出这些呢?
它是这样想的,我们上面讲的迭代器,比如都是调用begin,普通对象调用返回普通迭代器,const对象调用返回const迭代器,好像有点不清楚,它期望你普通对象就去调上面我们讲的不带c的那一套,const对象就调带c的那一套,规范一点。
但是呢,好像不是很必要,而且大家一般也不太喜欢用这些新的,所以这个大家了解一下就行了。
8. 元素访问
那首先呢就是[],string是重载了[]的,我们可以直接用:
然后这里想跟大家说的是:
operator[]也是有普通版本和const版本的,普通对象调[]就返回char&,可以去修改它,const对象就返回const char&,不能修改。
然后我们看到还有一个at:
at作用跟[]是一样的,而且它同样也有const和非const版本:
但是呢,它们两个还是有区别的,区别在于:
用[]如果越界访问的话是直接报错的,它内部是断言去判断的
但是at呢
我们看到是这样的,它其实是抛了个异常
异常是可以捕获的,但是现在我们还没学,大家先了解一些就行了。
但是在实际当中其实我们很少会用at。
然后还有一个back和front:
其实就是返回最后一个和第一个字符,但是这个我们用
[]就能搞定,所以大家简单了解一下就行了。
9. insert和erase
那到这里我们在回过头来看几个之前跳过的内容。
先来看一下insert:
借助
insert我们可以像string对象中插入字符和字符串
我们看到这里也是提供了好多版本,但是有的并不常用。
那我们来练习几个比较常用一点的。
现在有一个string对象s,那我们现在想在world前面插入一个字符串hello,怎么搞?
那我们就可以考虑用这个:
第一个参数指定我们要从哪个位置开始插入,第二个参数指定要插入的字符串:
然后我们又想在hello后面插入一个空格,怎么搞?
首先可以这样:
或者我们可以去调这个:
除此之外还有这个接口:
我们看到这个不是传下标,而是传目标位置的迭代器:
这样就可以了。
🆗,那然后大家思考一个问题:
对于string来说,大家觉得可以频繁使用insert吗?或者说经常用insert好不好?
那我们其实是不推荐经常使用insert的,为什么呢?
我们说了string底层是字符数组,那我们学过数据结构知道在顺序表里插入元素是不是要挪动数据啊,效率是比较低的,所以呢insert我们能少用就少用。
于insert对应,接下来我们看一下erase:
那erase呢其实就是去删除string对象里的元素。
举个栗子:
现在我们想删除s里面hello后面的空格,怎么搞?
可以用这个,从第5个位置开始删除一个字符:
然后我们看到这里len的缺省值是npos,npos我们之前是不是说过啊,在这里是整型最大值,所以这里如果我们传的len比字符串长度大,或者不传,它都会删除字符串结尾。
还可以用这个:
传迭代器的位置
然后还有一个接口涉及到迭代器区间,我们这里先不说。
那同样的道理,对于erase来说,如果我们只删除中间的一部分,是不是也要挪动数据啊,所以,erase也不推荐频繁使用。
10. replace、find、rfind、substr
我们再来了解一下replace:
replace其实就是可以把字符串中的一部分替换成新内容。
也有很多版本,我们不可能全部都讲,必要时大家可以自己查阅文档。
我们看这样一个场景:
现在想把s里面的空格替换成"%%d",怎么搞?
确实完成了,但是大家看,这样做真的好吗?
有什么问题?
首先可能会需要挪动数据,我们当前这个例子就有,其次,空间如果不够还得扩容,所以这个东西我们也尽量避免去用。
然后我们再来看一下find:
find可以在字符串里查找字串或者字符,返回对应的下标。
找不到返回npos
举个栗子:
在s里查找空格,怎么搞?
当然也可以查找字符串:
然后我们还看到:
它还给了一个缺省参数pos,缺省值是0,其实pos呢是用来指定我们开始查找的位置的,我们自己不指定那就默认从0开始,我们指定了,就从指定的位置开始找。
我们可以试一下:
大家看这个,我们要找ll,但是我们指定从下标5的位置开始找,那是不是就找不到了,所以返回npos,打印出来的就是npos对应的值。
那我们来看这样一个问题:
假设现在有一个文件名的字符串,比如说
string.cpp,我们想把后缀名取出来,怎么搞?
这时我们就可以考虑使用substr这个成员函数。
它的作用是什么呢?
它可以帮助我们获取string对象中指定的一个子串。
参数:
那有了substr,我们就可以怎么解决上面的问题:
我们是不是可以先用find找的.的位置,然后计算出后缀名的长度len,从.的位置开始,向后获取长度为len的字串。
int main()
{string s("string.cpp");size_t pos = s.find('.');if (pos != string::npos){string suf = s.substr(pos, s.size() - pos);cout << suf << endl;}return 0;
}
运行测试一下:
这不就拿到了嘛。
那再看,如果是这种情况呢?
它有多个后缀,但是我们只取最后一个,这下应该怎么办?
🆗,那除了我们上面学过的find,还有rfind:
那rfind和find有什么区别呢?
区别在于find是从前往后找第一个匹配项,而rfind是从后往前找倒数第一个匹配项。
所以当前这种情况,我们是不是把find换成 rfind就行了啊:
当然其实刚才这两个场景我们使用find和rfind可以不传第二个参数的
这里给了缺省值npos,也就是我们不传的话,它默认从我们给的位置一直取到结尾。
然后呢:
我们看到
substr后面还有一个compare,就是进行string对象之间的比较嘛,但是这个compare我们一般用不上。
因为string还重载了关系运算符
我们可以直接用来比较
但是这里我们看到光是==它就重载了3个版本,但是其实根本没必要,所以string类的设计其实是被吐槽过的:
11. swap与string::swap
string里面呢,还有一个成员函数叫做swap:
它可以接收一个string对象,与当前对象进行交换。
演示一下:
🆗,那除了这个swap之外,我们之前不是提到过说C++的库里面也有一个swap,它是一个模板函数。
所以我们也可以这样交换两个string对象:
🆗,也可以完成交换。
那大家来思考一下,这两个swap哪一个效率更高一点?
🆗,那这里肯定是string的swap是更高效的一点的,因为string::swap作为string的成员函数,那它里面想交换这两个对象,就可以怎么办,是不是可以直接改变指针的指向啊:
而库里面的这个swap是怎么交换的:
🆗,我们看到这里是构造一个临时变量,然后又有两个赋值,而string的拷贝是需要深拷贝的,所以它这里是比较低效的。
那这里我们先简单的说一下结论,后面我们模拟实现的时候会对这里有一个更深的理解。
12. c_str
下面我们再来看一个string的成员函数叫做c_str:
那它的作用是什么呢?
它其实是去返回一个指向当前string对象对应的字符数组的指针,类型为const char*。
所以,如果我们想打印一个string对象,就可以有这样两种方式:
你可以认为c_str返回的指针就类似于这里的指针p:
那这里第一个打印其实调的是string重载的<<:
第二个调的就是库里面的那个。
那上面那个例子我们看到两种方式打印出来没什么区别,那如果这样呢?
int main()
{string s1("hello world");s1 += '\0';s1 += '\0';s1 += "*******";cout << s1 << endl;cout << s1.c_str() << endl;return 0;
}
这时我们再用这两种方式打印:
我们看到结果就有区别了。
为什么呢?
因为第一种方式我们直接打印string对象s1,它是去看s1对应的size 的,size是多大,总共有多少字符,全部打印完。
但是我们第二种打印c_str返回的const char*的指针,它是遇到'\0'就停止了。所以大家可以理解成c_str就是返回C格式字符串。
所以这次就有差异了。
13. getline
我们来看这样一个场景:
int main()
{string s;cin >> s;cout << s << endl;return 0;
}
这里定义了一个string对象,现在我们要自己给它输入一个值,然后打印输出。
我们来试一下:
嗯???但是我们发现这里出现了一点问题。
我们输入的是hello world,但是为什么打印s出来只有hello啊,怎么回事?
🆗,大家要知道,C语言里的scanf,包括这里的cin,我们在用它们输入的时候是不是有可能输入多个值啊,那当我们输入多个值的时候,它们默认是以空格或者换行来区分我们输入的多个值的。
所以我们这里输入的hello world,会被认为是两个值以空格分隔开了,所以cin值读到了空格前面的hello,后面的world就被留在缓冲区了。
那这种情况怎么解决呢?
🆗,我们就可以用一个函数叫做——
getline
getline呢它读取到空格才结束,当然它还支持我们自己指定结束符。
第一个参数就是接收cin,第二个参数接收我们要输入的string对象。
我们试一下:
🆗,这下就可以了。
2.4 总结
那我们简单总结一下:
我们这篇文章关于string使用的讲解差不多就到这里了,string提供的接口是比较多的,我们不可能全部都讲完,当然其中大部分我们平时可能都不会怎么用到,常用的其实是比较少的,有些没讲到的后续如果大家有需要,可以查阅文档进行学习——链接: link
那我们这篇文章就到这里,欢迎大家指正!!!
后续我们还会对string进行模拟实现,到时候有些地方我们或许能够理解的更深刻一点。

相关文章:
【C++】STL简介 及 string的使用
文章目录1. STL简介1.1 什么是STL1.2 STL的版本1.3 STL的六大组件2. string类的使用2.1 C语言中的字符串2.2 标准库中的string类2.3 string类的常用接口说明1. string类对象的常见构造2. string类对象的容量操作3. string类对象的修改操作4. resize和reserve5. 认识迭代器&…...
MySQL事务详解
🏆今日学习目标: 🍀Spring事务和MySQL事务详解 ✅创作者:林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加入: …...
ChatGPT背后的技术和多模态异构数据处理的未来展望——我与一位资深工程师的走心探讨
上周,我和一位从业三十余年的工程师聊到ChatGPT。 作为一名人工智能领域研究者,我也一直对对话式大型语言模型非常感兴趣,在讨论中,我向他解释这个技术时,他瞬间被其中惊人之处所吸引🙌,我们深…...
iOS-砸壳篇(两种砸壳方式)
CrackerXI砸壳呢,当时你要是使用 frida-ios-dump 也是可以的; https://github.com/AloneMonkey/frida-ios-dump frida-ios-dump: 代码中需要更改的:手机中的内网ip 密码 等 最后放到我的砸壳路径里: python dump.py -l查看应用…...
linux 基础
1.Shell 命令的格式如下:command -options [argument]command: Shell 命令名称。options: 选项,同一种命令可能有不同的选项,不同的选项其实现的功能不同。argument: Shell 命令是可以带参数的,也可以不带参…...
Java:SpringBoot给Controller添加统一路由前缀
网上的文章五花八门,不写SpringBoot的版本号,导致代码拿来主义不好使了。 本文采用的版本 SpringBoot 2.7.7 Java 1.8目录1、默认访问路径2、整个项目增加路由前缀3、通过注解方式增加路由前缀4、按照目录结构添加前缀参考文章1、默认访问路径 packag…...
Java 基于 JAVE 库 实现 视频转音频的批量转换
文章目录 Java 基于 JAVE 库 实现 视频转音频的批量转换Maven:方案一:代码优化:方案二:示例代码:代码优化:结语Java 基于 JAVE 库 实现 视频转音频的批量转换 实现视频转音频的功能需要使用到一个第三方的 Java 库,叫做 JAVE。JAVE 是一个开源的 Java 库,提供了视频和音频转换…...
Spring容器——基于XML注入
1. 容器:IOC IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序 Spring 通过 IoC 容器来…...
设计模式(二十一)----行为型模式之状态模式
1 概述 【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能…...
一分钟理解 AP(Affinity Propagation) 亲和⼒传播算法
从来没有一个算法让我研究好几天都搞不明白,AP算法算是第一个。弄了好几天,打草纸用了几十页,反复琢磨,最后都怀疑人生了。我觉得网上那么多介绍 AP 的文章,基本上没有一篇能讲明白的。最后我都觉得 AP 的作者可能都没…...
使用mybatis的映射文件操作存储过程
先随便创建一个存储过程 DELIMITER $$ CREATE PROCEDURE getUserNameById (IN i_id BIGINT, OUT o_name VARCHAR(10)) BEGINSELECT u.name INTO o_name FROM tb_user u WHERE id i_id; END $$delimiter $$ : 是将sql语句的结束符号先替换成$$的意思,因为sql是遇到…...
世界上最完美的两个软件,太厉害了!
今天给大家介绍两个软件,一个体现了人类在软件开发流程上的极致,另外一个则体现了程序员个体能力的巅峰。01航天飞机飞控软件先来说第一个,航天飞机飞行控制软件,就是下图这个大家伙。航天飞机重达120吨,还携带着2000吨…...
教你成为比卡卡西还牛逼的全能忍者,全拷贝与分割函数
如何成为一个集雷切,写轮眼侦查和拷贝与一身的卡卡西,下面教你! 目录 第一式——雷切! strtok 第二式——写轮眼侦查! strerror函数 第三式——写轮眼拷贝! memcpy 模拟实现memcpy函数 😎…...
【LeetCode】剑指 Offer(24)
目录 题目:剑指 Offer 47. 礼物的最大价值 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 47. 礼物的…...
javaEE 初阶 — CSS 元素的显示模式与盒模型
文章目录1. 元素的显示模式1.1 块级元素1.2 行内元素1.3 行内元素和块级元素的区别1.4 改变显示模式2. 盒模型2.1 边框2.1.1 边框的粗细2.1.2 边框的颜色2.1.3 边框的风格2.2 内边距2.3 外边距2.3.1 margin 的特殊情况1. 元素的显示模式 1.1 块级元素 常见的元素: h1 - h6 、…...
新星计划-我为什么要写博客?写博客的意义是什么
CSDN的各位友友们你们好,今天千泽要和大家交流一下写博客的意义,并且鼓励大家参加CSDN官方举办的新星计划,这个可以让我们更快的成长,十分有价值.接下来让我们一起开始吧!如果对您有帮助的话希望能够得到您的支持和帮助,我会持续更新的!🚩part1:自我介绍我是一名来自…...
嵌入式学习笔记——STM32的USART收发字符串及串口中断
USART收发字符串及串口中断前言字符串的收发发送一个字符串接收字符串需求利用串口实现printf中断中断是什么前言 上一篇中,介绍了串口收发相关的寄存器,通过代码实现了一个字节的收发,本文接着上面的内容,通过功能函数实现字符串…...
数据分析之Pandas(1)
3.Pandas 文章目录3.Pandas3.1 Pandas基本介绍3.1.1 Pandas的基本数据结构3.1.1.1 Pandas库的Series类型3.1.1.2 Pandas库的DataFrame类型DataFrame初始化DataFrame查看数据3.1.2 Pandas读取数据及数据操作行操作添加一行删除一行列操作增加一列删除一列通过标签选择数据条件选…...
17、江科大stm32视频学习笔记——USART串口协议和USART串口外设
目录 1、通信接口 2、 硬件电路 3、电平标准 4、串口参数及时序 5、USART简介 6、USART工作 (1)写操作 (2)读操作 (3)帧头和帧尾的添加和除由电路自动执行 (4)硬件数据控制…...
leetcode:有效地括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
Mac flutter环境搭建
一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...
大模型真的像人一样“思考”和“理解”吗?
Yann LeCun 新研究的核心探讨:大语言模型(LLM)的“理解”和“思考”方式与人类认知的根本差异。 核心问题:大模型真的像人一样“思考”和“理解”吗? 人类的思考方式: 你的大脑是个超级整理师。面对海量信…...
免费批量Markdown转Word工具
免费批量Markdown转Word工具 一款简单易用的批量Markdown文档转换工具,支持将多个Markdown文件一键转换为Word文档。完全免费,无需安装,解压即用! 官方网站 访问官方展示页面了解更多信息:http://mutou888.com/pro…...
比较数据迁移后MySQL数据库和ClickHouse数据仓库中的表
设计一个MySQL数据库和Clickhouse数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...














































































































































