前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【C++】STL全面简介与string类的使用(万字解析)

【C++】STL全面简介与string类的使用(万字解析)

作者头像
TANGLONG
发布于 2025-03-13 00:37:25
发布于 2025-03-13 00:37:25
20000
代码可运行
举报
运行总次数:0
代码可运行

一、STL简介

1. 什么是STL

    STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

    简单的说就是STL中包含了各种我们之前写过的数据结构与方法,如顺序表、链表等等,被成为STL容器,其中还包含一些简单的算法,如查找、排序、交换等等,以及一些很常用的东西,我们下面都都会一一介绍

2. STL的版本

    STL的版本有很多,我们介绍几个常用的STL版本,如HP、PJ、RW、SGI版本,我们后面在学习STL的时候会以SGI版本为基础进行学习

    - 原始版本(HP):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要阅读部分源代码,主要参考的就是这个版本

3. STL的组成

    STL的组成包含六大组件,我们用下面的图给大家看看大致分类,然后再细化进行讲解:

    1. 仿函数:它其实是一个类,只是重载了operator(),这样就可以把它当作函数使用,这种类就被称为仿函数,我们在STL的学习中会经常用到,但是现在可以不用过多了解,知道概念就行

    2. 算法:STL中包含一些常见的算法,使得我们不必为了一些场景反复造轮子,让我们在写代码时更加轻松

    3. 容器:其实就是我们在初阶数据结构与算法中学习的数据结构,只是将这些数据结构写成了模版,使得可以装下任意类型,也是为了避免反复造轮子,我们直接使用就可以了

    4. 迭代器:有的容器之间差异比较大,为了提供一种通用的遍历容器的方式,C++就设计出了迭代器,它的作用就是可以对任何容器进行遍历,并且语法相同

    5. 空间配置器:它涉及到内存池的概念,通俗来讲就是基本上容器都会涉及到扩容,每次扩容都要去堆上申请空间,堆就很忙了,最后导致效率降低,为了提高效率,可以一次性申请一大段空间预留在那里,以后申请空间直接去找这些预留的空间,不用去打扰堆了,这些预留的空间就是内存池

    6. 配接器:也叫适配器,它们的特点是可以适配其它容器,在其它容器的基础之上进行实现,我们后面讲到stack和queue的时候就知道了

    上面就是STL的六大组成,STL可以说是C++非常重要的部分,在平常的使用以及笔试面试中都非常常用,网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发

4. 如何学习STL

学习STL可以分成三个阶段,分别是:     1. 熟练使用STL     2. 了解STL底层以及泛型编程的内涵     3. 扩充STL

    我们可以将这三个阶段比作以下的三张图片:

    在第一阶段,我们相当于只是一个婴儿一般,只会简单的爬行,也就是会用,在第二阶段我们就能够走起来,也就是简单的能明理,能够明白STL的实现,最后一个阶段我们可以跑起来了,也就是能扩展,真正将STL掌握

    我们在讲解时会尽力将大家引领到第二阶段,我们首先会学习STL接口的使用,然后再来将它们实现一遍,以求能够明理,这样其实就够我们用了,最后一个阶段就要看自己个人的兴趣和需求了,需要我们自己下来慢慢体会

二、string类的使用

    string类就是一个字符串类,提供了各种与字符串相关的方法供我们操作字符串,严格来说string类并不属于STL,因为STL是标准模板库,是各种模板,而string类不是一个模板,是一个已经确定类型的类,但是虽然string类不属于STL的严格定义范围,但它在C++标准库中与STL有着紧密的联系和协同工作,是C++编程中不可或缺的一部分

    可以这样说,string类是STL的一个具体特例,与STL容器不同的就是类型已经确定,其它和STL其它容器的思想都大致相同,所以我们先来学习一下string类的使用,下一篇学习然后慢慢过渡到真正的STL

    string类可以看作是一个类型为char的顺序表,结构和许多接口与我们之前学习顺序表的结构和接口类似,我们在C语言部分学习初阶数据结构与算法就是为了更好地学习STL,由于结构和接口类似,所以这一部分其实并不难

    首先我们要结合一个网站学习,这个网站就是之前给大家推荐的https://legacy.cplusplus.com/,之后我们学习都要结合这个网站,我们搜索string就可以找到它了,如下:

    这里的接口有很多,我们暂时只学习一些常用的重要接口,相比之下不会太多,也不要太担心,因为很多接口我们之前在初阶数据结构部分的顺序表就已经学过了,甚至结构基本一样,都是一个数组以及size和capacity组成,只是数组类型为char,接下来我们按照上面给出的顺序一一进行讲解

第一部分

    第一部分的接口如图:

这些都是string类的默认成员函数的重载,我们把它们都简单过一遍就差不多了

    1. 构造函数:分为默认构造和普通构造函数,string类的默认构造就是生成一个空串,然后将对应的size和capacity置为0,普通构造就可以传字符串去构造string类对象,我们写个程序来把两种情况调试看看,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
//使用string类要包含头文件string
#include <string>
using namespace std;

int main()
{
	//默认构造
	string s1;
	//普通构造
	//也可以写作string s2 = "zhangsan"
	string s2("zhangsan");
	return 0;
}

    调试分析如图:

    2. 析构函数:析构函数就是专门释放掉这个string类存放数据的数组,在类和对象的析构函数我们讲过,这里不再多说

    3. 拷贝构造与赋值重载:对string类实现深拷贝,这里我们就不再演示了,在模拟实现会进行详细讲解

第二部分

    接下来就是第二部分,接口如图:

    上面的第二部分就是所有接口中比较重要的接口,这些接口主要用于修改,比如可以插入和删除元素,首先我们来看最简单的,也是我们最熟悉的push_back()与pop_back(),这里的命名规则和我们之前的命名规则差不多,所以很好懂,它们的作用分别是尾插一个字符与尾删一个字符,不能尾插字符串,我们写一个示例,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello");
	s1.pop_back();
	s1.push_back('w');
	s1.push_back('o');
	cout << s1 << endl;
	return 0;
}

    按照我们的预期,第一次pop_back后会删除最后一个字符o,然后尾插两个字符wo,最后答案应该是“hellwo”,我们来看看代码运行结果:

    可以看到代码运行结果如我们所料,接下来就是重载的+=运算符,它的作用就相当于尾插,不过既可以+=字符也可以+=字符串,而push_back只能尾插一个字符,我们来试着用一下,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 int main()
{
	string s1("hello");
	//尾插一个空格字符
	s1 += ' ';
	//尾插一个字符串
	s1 += "world!";
	cout << s1 << endl;
	return 0;
}

    根据之前的学习,我们能够猜到这段代码的结果为“hello world!”,我们来看看运行结果,如下:

    可以看到没有问题,这个运算符重载非常好用,接下来就是append函数,它也是一个尾插函数,我们可以来看看它有哪些重载,如下:

    我们可以看到这里的接口设计的非常多,但是唯独不能尾插字符,虽然可以尾插只有一个字符的字符串,但是还是感觉怪怪的,push_back只能尾插字符,而append又只能尾插字符串,所以还是重载的运算符+=好用,既可以尾插字符又可以尾插字符串,可读性还高,但是我们还是要学习一下这个函数,接下来我们举一个例子,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//这段代码的运行结果是什么?
int main()
{
	string s1("hello");
	string s2(s1);
	s1.append(" world");
	s2.append("world", 1, 2);
	cout << s1 << endl;
	cout << s2 << endl;
	return 0;
}

    大家可以自己计算一下上面程序的结果,然后再来看分析与答案,首先我们来看s1,它尾插了一个 world,很简单,最后s1应该是"hello world",然后我们来看s2,它使用的是append的第二个接口,比较容易搞混

    因为位置是从0开始数的,很多人认为是从1开始数的,就以为答案是"hellowo",但其实位置是从0开始数的,相当于数下标,所以上面代码是从o字符开始的两个字符,也就是or,所以s2输出的结果应该是“helloor”,我们来看看代码运行结果:

    可以看到我们分析的没有问题,我们接下来继续学习insert和erase这两个接口,它们的作用是在指定位置插入和删除元素,如下图:

    可以看到insert的接口非常多,而erase的接口倒是比较好掌握,接下来我们简单写个测试程序练练手,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//下面代码输出的结果是什么?
int main()
{
	string s1("hello");
	s1.insert(1, "world", 1, 2);
	cout << s1 << endl;
	
	s1.insert(2, "123");
	cout << s1 << endl;
	
	s1.erase(2, 2);
	cout << s1 << endl;
	
	s1.erase();
	cout << s1 << endl;
	return 0;
}

    首先我们使用的第一个insert的含义是在1下标位置处插入字符串"world"从1号下标处开始的两个字符,所以第一次输出应该是horello

    第二个insert的含义是在2下标处插入字符串"123",所以第二次输出应该是ho123rello,第一个erase的含义是从2下标处开始删除两个字符,应该输出ho3rello,第二个erase的含义就是从0下标处直接删除所有元素,应该什么都没有打印,我们来看看代码运行结果:

    可以看到和我们分析的完全一致,接下来我们来简单过最后两个要讲的修改接口,也就是replace和assign,它们的作用分别是替换和赋值,其中assign比较简单,它的作用和赋值重载差不多,但是其实它比赋值重载效率更高一些,但是一般来讲使用谁都行

    至于replace和insert和erase的接口设计很相似,我们来看看它们的接口:

    接下来我们简单写个程序尝试一下,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//下面代码
int main()
{
	string s1("hello");
	//horello
	s1.insert(1, "world", 1, 2);
	s1.replace(1, 2, "12345");
	cout << s1 << endl;

	string s2;
	s2.assign("123");
	cout << s2 << endl;
	s2.assign("hello world");
	cout << s2 << endl;
	return 0;
}

    这里我就不给出分析过程了,因为replace的使用和insert差不多,而assgin又非常简单,所以我们这里直接给出代码运行结果,但是大家最好还是先分析出结果再来看答案,如下:

    你的分析正确了吗?欢迎在评论区留言,那么修改接口我们就讲到这里,其中有个swap我们会在下一篇string的实现中讲解,我们进入下一个部分

第三部分

    第三部分非常常用,但是比较简单,接口也不多,如下:

在这里插入图片描述
在这里插入图片描述

    其中operator[]就是重载的运算符[],让我们使用string类可以像平常的字符串一样访问元素,而at和它的效果一致,at函数的参数就是要访问的元素的下标,它们的区别就是at函数有越界检查,而[]重载则没有,我们先来看看它们的使用,如下:

    可以看到我们可以像访问数组一样访问试听类的元素,可读性很高,接下来我们来看看[]重载和at函数对于越界访问的检查,如下:

    我们可以看到,使用[]时程序并没有问题,而一旦使用at程序就直接中断了,所以at的安全检查是更加到位的,但是某种情况下它的效率也是要更低一些的,所以[]适合大量数据的访问并且确保下标有效的场景,而at适用于需要安全性较高的场景

    那么[]和at讲完了接下来就是更简单的两个接口front和back,从名字上我们就知道了它的作用,一个是取出第一个字符,一个是取出最后一个字符,如下:

    可以看到确实如我们所料

第四部分(含auto与范围for)

    第四部分就是我们比较重要的迭代器部分,接口如下:

    由于大家第一次接触迭代器,所以我们这里讲清楚一点,每一个容器都有自己的迭代器,迭代器可以用来遍历容器,它的具体类型要看是哪个类,因为迭代器是封装在一个类里面的,我们要写出一个迭代器的类型就要突破类域,比如string的迭代器就是string::iterator

    而begin()函数的作用就是返回这个容器的第一个元素的迭代器,而end()就是返回这个容器的最后一个元素的迭代器,虽然迭代器类型不同,但是迭代器的使用方法相同,就是按照指针的方式使用,在使用的层面可以认为begin()就是第一个元素的地址,end()就是最后一个元素的下一个元素的地址

但是请注意,我们上层可以这样认为,不代表它的底层就是这样的,迭代器可以像指针一样使用是因为容器将其进行了封装,封装成一个类,然后重载这个类的运算符*和->等,当然也有直接把地址当作迭代器使用的,比如string和vector就是,这个要等我们讲到底层实现的时候才能说明白

    现在我们就只需要知道迭代器的使用跟指针差不多就可以了,接下来我就带大家使用迭代器遍历一个string类,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello world!");
	//相当于it就是第一个元素的地址(上层可以这样认为)
	string::iterator it = s1.begin();
	//常见的遍历s1的写法(只要it没有走到最后一个元素的下一个元素就没有结束)
	while (it != s1.end())
	{
		//将it看作指针使用,解引用拿到对应的字符
		cout << *it << " ";
		//让it++到下一个元素的位置
		it++;
	}
	cout << endl;
	return 0;
}

    按照我们的预期,我们可以遍历整个s1,我们再以指针的角度画一个示意图,如下:

    有了这个示意图我们就好懂多了,接下来我们来看看代码的运行结果是否能够遍历整个string,如下:

    可以看到确实如我们所料,那么既然可以遍历,我们是不是可以对其中的元素作修改,我们来修改一下,一边遍历一边修改,如下:

    可以看到迭代器的使用就跟指针一样,既可以访问数据也可以修改数据,那么问题来了,如果我们不想要使用迭代器的时候修改数据怎么办呢?这个时候我们就可以使用const迭代器,我们来看看const迭代器是如何定义的,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
string::const_iterator it = s1.begin();

    其实跟普通迭代器很像,只是加了一个const_,这样一写我们就得到了const迭代器,这个时候我们可以解引用访问数据,但是不能修改,如下:

    上面我们演示的迭代器也叫正向迭代器,它是从正向开始遍历的,还有一种反向迭代器,它就从后往前进行遍历,其它的使用和正向迭代器差不多,可以访问也可以修改,当然要看是不是const迭代器,我们来看看反向迭代器是怎么定义的,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello world!");
	//反向迭代器的定义
	string::reverse_iterator rit = s1.rbegin();
	//const反向迭代器的定义
	//string::const_reverse_iterator rit = s1.rbegin();
	//反向迭代器的使用和正向一致
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		//注意是++不是--,和正向迭代器使用方法一致
		//正向迭代器++是从前往后走,反向迭代器++是从后往前走
		rit++;
	}
	cout << endl;
	return 0;
}

    这里注意一下,从后往前走是++不是–,和正向迭代器使用方法一致,正向迭代器++是从前往后走,反向迭代器++是从后往前走,接下来我们来看看代码运行结果,看看是否能倒着遍历s1,如下:

    可以看到符合我们的倒着遍历的预期,在讲完迭代器之后,我们会感觉迭代器的类型怎么这么长啊,有没有什么办法更方便呢?有的有的兄弟,接下来我们就请出auto,它可以自动帮我们识别类型,让我们不用写这么长的类型名,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello world!");
	//auto自动推导识别类型
	auto it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}

    我们来看看代码运行结果:

    auto是不是非常好用呢?它是C++11设计出来的语法糖,可以让我们不用写那么长的类型名,接下来我们再来介绍一个C++11提出的语法糖,也就是范围for,它的作用是给我们提供一个快捷方便的遍历方式,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello world!");
	//auto自动推导识别类型
	auto it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//这里可以是auto,也可以自己写类型
	//如果取出的e非常大,也可以加引用,即auto& e : s1
	for (auto e : s1)
	{
		//e就是s1中取出的元素
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

    我们来看看代码运行结果:

    可以看到auto加范围for也非常好用,我们需要记住两点,一点就是范围for的格式,另一点更重要的是我们要知道范围for的底层其实还是迭代器,当我们给一个类实现了迭代器,那么就可以使用范围for进行遍历,是不是非常神奇呢?

    那么关于迭代器的知识我们就讲到这里,在后面的实现中我们还要继续学习它的底层

第五部分

    我们来看看第五部分有哪些接口,如下:

    这个部分也很简单,跟容器的容量有关,我们只需要掌握部分接口,其中size()返回当前容器的有效数据个数,而capacity()是返回容器当前的容量,empty()是判断容器是否为空,clear()是清空容器,接下来我们先把这四个函数测试一下,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void IsEmpty(const string& s)
{
	bool ret = s.empty();
	if (ret)
		cout << "空" << endl;
	else
		cout << "非空" << endl;
}

int main()
{
	string s1("hello world!");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	IsEmpty(s1);
	//清空s1,也就是清空有效数据
	s1.clear();
	IsEmpty(s1);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}

    我们来看看代码运行结果:

    可以看到符合我们的预期,接下来我们知道了可以计算容器中有效元素的个数,那么我们遍历string类对象的方法就多了一种,之前我们介绍了两种,一种是迭代器,一种是基于迭代器的范围for,我们这里介绍的第三种就是利用数组的方式进行遍历,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello world!");
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
	cout << endl;
	return 0;
}

    我们来看看运行结果:

    可以看到代码没有问题,以后我们就也可以用这种类似于数组遍历的方式遍历string类对象,那么接下来我们认识几个新接口,一个是resize,一个是reserve,其它的不太常用我们就不再介绍了,其中resize是修改有效数据个数,而reserve则是扩容,我们来看看它们的具体接口:

    我们先来介绍reserve,它的情况要少一点,reserve的作用是扩容,如果我们传过去的参数小于当前容器的容量,那么它什么也不会做,只有当我们传过去的参数大于当前容器的容量时才会进行扩容,并且扩容时的逻辑是至少扩容到那个容量,可能会多几到十几的容量,我们写个代码来用于调试,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello");
	s1.reserve(2);
	s1.reserve(30);
	return 0;
}

    上面的三张图片分别对应着初始情况、执行reserve(2)后的情况以及执行reserve(30)后的情况,可以看到初始情况和执行reserve(2)后的情况没有任何区别,因为2小于当前的容量,reserve什么也不会做,而到reserve(30)才可以扩容

    但是我们发现实际上容量不是30,而是31,这是因为我们刚刚说的,扩容是至少扩容到那个容量,可能会多几个到十几个容量,具体要看我们扩容的数量与编译器的实现,反正我们需求的容量一定会达到,这就是reverse,接下来我们介绍resize

    resize的作用是修改size,它的情况就要多一些了,一共有三种,我们画个图来看看第一种是什么,如下:

    这种情况很明显是将size缩小了,此时resize的作用就是删除,只保留前n个元素,我们写个程序验证一下,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello world!");
	cout << s1 << endl;
	s1.resize(2);
	cout << s1 << endl;
	return 0;
}

    按照我们的预期,一旦size被修改为2,那么会删除后面所有元素,只剩两个元素,也就是he,我们来看看代码运行结果:

    可以看到符合我们的预期,接下俩我们来看第二种情况,如下:

    这个时候n处于size和capacity之间,那么size到n这段空间就会根据具体接口填充对应的值,如果我们直接使用第一个接口,也就是只指定size的调整大小,也就是n的大小,那么默认就会填充/0,如果使用第二个接口,那么中间填充的就是我们传过去的那个字符,我们写个程序证明,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello");//size为5
	string s2 = s1;
	s1.resize(10);

	s2.resize(10, 'x');
	return 0;
}

    我们来调试一下代码看看是否如我们所说,如下:

    可以看到跟我们描述的一致,如果使用第一个接口就默认填充/0,如果使用第二个接口就按照第二个接口传来的字符填充,接下来我们来看第三种情况:

    这种情况就是n直接超过了容量,这个时候的逻辑也比较简单,这个时候会对string对象进行扩容,扩容逻辑也是至少有n个空间,从capacity到n的空间还是根据具体的接口初始化,如果是第一个接口就全是\0,如果是第二个接口传了字符,那么就全部填充这些字符,我们来实践一下,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello");
	string s2(s1);
	s1.resize(20);

	s2.resize(20, 'x');
	return 0;
}

调试结果如下:

    可以看到size是直接到20的,中间填充的东西也跟我们上面讲的一样,而容量也是至少会扩容到n个空间,那么关于容量的这个部分我们就讲到这里,接下来我们来看下一部分

第六部分

    我们来看看第六部分相关字符串操作有哪些接口:

    这个部分虽然有这么多接口,但是我们暂时只掌握五个接口,如图:

    首先是第一个接口c_str,这个比较简单,它的作用就是返回string中的那个可以当做数组使用的指针,但是要注意是const char*的,因为不想再外部更改它,通过 c_str(),C++ 的string 类能够与C风格的字符串函数无缝地互操作,接下来我们来测试一下,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//等价于#include <string.h>
#include <cstring>
int main()
{
	string s1("hello");
	const char* CString = s1.c_str();
	cout << CString << endl;
	cout << strlen(CString) << endl;
	return 0;
}

    我们来看看代码运行结果,如下:

    可以看到确实做到了C++的string类和C的字符串的无缝衔接,接下来我们来find和rfind的接口,如下:

    它们两个的作用也很容易看出来,就是查找,一个是正向查找,一个是反向查找,并且支持指定从哪个下标处开始查找,如果找到了就返回这个字符的下标或者这个字符串的起始下标,如果没有找到就返回npos,npos我们在前面介绍过,就是一个非常大的静态成员变量,接下来我们举个具体的例子,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello");
	//默认从0下标开始查找
	size_t pos1 = s1.find("he");
	//从2下标处开始查找
	size_t pos2 = s1.find("he", 2);
	cout << pos1 << endl;
	cout << pos2 << endl;
	return 0;
}

    我们来看看程序运行结果:

    从返回值我们就可以看出来,pos1是找到了,而pos2没有找到,返回了npos,这是因为第一个find默认从0下标开始找,而第二个find是我们指定从2下标处开始找,所以一个找到了一个没找到,rfind和这个同理,只是说是倒着找

    接下来就是字符串截取函数substr,这个函数在做题的时候很常用,如下:

    具体含义就是从pos下标处开始截取len长度的字符串,默认情况下是从0号下标截取到npos,其实就是截取整个字符串,也可以自行指定开始和长度,我们来尝试用一用,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1("hello");
	//从0号下标截取到npos,其实就是截取整个字符串
	string s2 = s1.substr();
	//从1号下标开始,截取长度为2的字符串
	string s3 = s1.substr(1, 2);

	cout << s2 << endl;
	cout << s3 << endl;
	return 0;
}

    可以看到和我们描述的一致,这个接口比较重要,建议一定要掌握,接下来就是这个部分的最后一个接口compare,作用就是比较两个字符串,这个接口也很简单,它的底层就是strcmp,返回值也是按照strcmp设计的,我们来具体看看,如下:

    这个比较简单,我们就不再多说了,我们来看最后一个部分,也就是非成员函数重载

第七部分

    在第七部分都是非成员函数重载,如下:

    这里我们只介绍一个函数,就是getline,其它函数要在实现部分才讲的清楚,getline的作用就是获取一行的输入,cin碰到空格就会结束,所以为了读取一行我们可以使用getline,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string s1;
	//以换行符为分隔,直到读取到\n才结束
	getline(cin, s1);
	cout << "第一个getline:" << endl;
	cout << s1 << endl << endl;

	//以#号为分隔符,直到读取到#才结束
	getline(cin, s1, '#');
	cout << "第二个getline:" << endl;
	cout << s1 << endl;
	return 0;
}

    我们来看看代码的运行结果:

    根据代码的运行结果我们可以看出,第一个getline没有给分隔符确实就是以换行为结尾,空格也会读取,而cin就不会读取空格,而第二个getline我们给出分隔符后,空格和换行都不能结束,直到我们输入指定的分隔符#才结束,这就是getline的作用,可以读取一行,也可以根据情况指定分隔符

    那么今天关于STL简介和string类的使用就讲到这里了,有什么疑问欢迎在评论区提问,今天就到这里,bye~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
3DSCTF RE Scared Flag
用户1423082
2024/12/31
360
3DSCTF RE Scared Flag
PHP-FPM RCE (CVE-2019-11043)
安全研究员 Andrew Danau 在解决一道 CTF 题目时发现,向目标服务器 URL 发送 %0a 符号时,服务返回异常,疑似存在漏洞。当 Nginx 将包含 PATH_INFO 为空的参数通过 FastCGI 传递给 PHP-FPM 时,PHP-FPM 接收处理的过程中存在逻辑问题。通过精心构造恶意请求可以对 PHP-FPM 进行内存污染,进一步可以复写内存并修改 PHP-FPM 配置,实现远程代码执行。
wywwzjj
2023/05/09
1K0
PHP-FPM RCE (CVE-2019-11043)
代码审计-dubbo admin <=2.6.1远程命令执行漏洞
通过结构化的思维进行以软件程序为中心的威胁建模、枚举威胁、缓解威胁、验证来解决四个问题:具体业务是什么?哪些地方可能出现风险?如何规避解决?是否覆盖完整。 通过前排了解(包括在fofa、zoomeyes、shodan的范围分析、wooyun历史漏洞材料输入),考量以下方面: - 数据流或代码布局; - 访问控制; - 现有的或内置的安全控制; - 非用户输入的入口点; - 与外部服务的集成; - 配置文件和数据源的位置; - 插件和定制化展现(在内置设计框架的情况下)。
安全乐观主义
2019/11/19
3.7K0
对Log4Shell CVE-2021-44228 应急响应
PCRE RegEx 在您的日志中匹配 Log4Shell CVE-2021-44228 IOC
Khan安全团队
2021/12/21
3500
对Log4Shell CVE-2021-44228 应急响应
记一次WordPress网站.htaccess文件感染病毒
今天碰到一个比较厉害的病毒,会自动感染网站目录下面的index.php和.htaccess等文件,感染之后网站打不开,其他的影响不清楚,这里记录一下病毒分析和解决方法。
子润先生
2021/06/09
8320
[pwnable.tw] MnO2 指定字符组合shellcode构造
究极丧心病狂的题,只能使用元素周期表组合以及数字填充进行shellcode构造。顺带一提,题目名字二氧化锰的来由是写入shellcode的固定地址转ascii转译后的结果。
赤道企鹅
2022/08/01
2020
Ursnif针对意大利公司的新攻击
Ursnif 是十分活跃的威胁之一,通常针对意大利和欧洲多个行业发起垃圾邮件攻击。
FB客服
2020/05/25
1.1K0
Ursnif针对意大利公司的新攻击
ssh-keygen生成的id_rsa文件的格式
Your identification has been saved in /home/gemfield/.ssh/id_rsa. Your public key has been saved in /home/gemfield/.ssh/id_rsa.pub
战神伽罗
2019/11/26
5.1K0
第二届网刃杯网络安全大赛
最终得到两个key拼接得到md5(3327104),得到flag{31a364d51abd0c8304106c16779d83b1}
故里[TRUE]
2023/04/19
2870
第二届网刃杯网络安全大赛
【漏洞实战】Apache Shiro反序列化远程代码执行复现及“批量杀鸡”
利用vmware workstation,安装一个操作系统,执行以下指令,更换系统源,并且安装docker
用户1631416
2019/12/19
3.1K1
【漏洞实战】Apache Shiro反序列化远程代码执行复现及“批量杀鸡”
深入认识二进制序列化--记一次生产事故的思考
二进制序列化是公司内部自研微服务框架的主要的数据传输处理方式,但是普通的开发人员对于二进制的学习和了解并不深入,容易导致使用过程中出现了问题却没有分析解决的思路。本文从一次生产环境的事故引入这个话题,通过对于事故的分析过程,探讨了平时没有关注到的一些技术要点。二进制序列化结果并不像Json序列化一样具备良好的可读性,对于序列化的结果大多数人并不了解,因此本文最后通过实际的例子,对照MSDN的文档对于序列化结果进行详细解析,并意图通过本次分析对于二进制序列化的结果有直观和深入的认识。
梁规晓
2019/07/09
4740
深入认识二进制序列化--记一次生产事故的思考
文件I/O (二).结构体存取(3)
结合前面的代码,从这个二进制编码里,我们可以看出很多有价值的信息 1.这是一个小端序的系统(数据的低字节保存在内存的低地址中) 2.每一个结构体占用了16字节 3.0-3 对应 int 的存储位置,4-8 对应 char[5] 的存储位置,12-15 对应 int 的存储位置 4.9-11 被空置了 这个命令将 ASCII 可显示的部分进行了显示,无法显示的都转化成了点 ---- ASCII 码 在Linux中使用man命令可以看到一份完整的ASCII码表 emacs@ubuntu:~/c$ man as
franket
2021/09/16
2400
FlexPaper <= 2.3.6 RCE远程代码执行漏洞分析附POC
这个漏洞的编号是CVE-2018-11686,但是漏洞本身没有公开,最近看到了exp自己跟着分析了一下。补丁可以在新版本中找到。新的版本2.3.7
用户5878089
2019/07/25
1.7K0
FlexPaper <= 2.3.6 RCE远程代码执行漏洞分析附POC
S2-057 远程命令执行复现
Apache wiki更新了一个Struts2的远程代码执行漏洞(S2-057),漏洞威胁等级为高危,漏洞对应的CVE编号为CVE-2018-11776。
LuckySec
2022/11/02
4490
S2-057 远程命令执行复现
常用编码表
Text-to-speech function is limited to 200 characters
botkenni
2019/09/03
1K0
常用编码表
漏洞分析】Shiro RememberMe 1.2.4 反序列化导致的命令执行漏洞
概述 Apache Shiro 在 Java 的权限及安全验证框架中占用重要的一席之地,在它编号为550的 issue 中爆出严重的 Java 反序列化漏洞。下面,我们将模拟还原此漏洞的场景以及分析过程。 0x01 漏洞场景还原 首先,需要获取 Apache Shiro 存在漏洞的源代码,具体操作如下: git clone https://github.com/apache/shiro.git git checkout shiro-root-1.2.4 cd ./shiro/samples/web
Seebug漏洞平台
2018/03/29
11.6K0
漏洞分析】Shiro RememberMe 1.2.4 反序列化导致的命令执行漏洞
python:过滤字符串中的字母数字特殊
字符串.isalnum()  所有字符都是数字或者字母,为真返回 Ture,否则返回 False。 字符串.isalpha()   所有字符都是字母,为真返回 Ture,否则返回 False。 字符串.isdigit()     所有字符都是数字,为真返回 Ture,否则返回 False。 字符串.islower()    所有字符都是小写,为真返回 Ture,否则返回 False。 字符串.isupper()   所有字符都是大写,为真返回 Ture,否则返回 False。 字符串.istitle()      所有单词都是首字母大写,为真返回 Ture,否则返回 False。 字符串.isspace()   所有字符都是空白字符,为真返回 Ture,否则返回 False。
py3study
2020/01/19
3.4K0
内网 HTTPS 可信证书
  开发团队或者公司内部一般会采用内外网隔离、上网行为过滤等措施,比较可靠地保证了内部设备无法被外部网络所侦测,从而可能认为 HTTP 内网站点是一个相对安全的存在。即使在 HTTPS 证书如此盛行的今天,也还暂时不考虑内部站点的 HTTPS 化。IP + Port 或者 http://本地域名 的访问方式依旧是座上宾。当然,如果考虑到购买 HTTPS 证书的成本或者团队内网站点采用 Letsencrypt 等免费证书过于麻烦(只能采用 DNS 验证的方式每三个月申请一次新证书),那么自签名 SSL 证书则成为首选了。不过,如果为每一个内网站点都生成一个 SSL 证书,然后让大家都手动把 HTTPS 标为可信,那么当面临大量内网站点时,大家可能要被搞崩溃。更为可行的办法是,生成一个内网用的根证书,只标记该根证书可信。
zhonger
2022/10/28
6.5K0
内网 HTTPS 可信证书
kubernetes init流程概览
kubernetes init流程 引导前检查 生成私钥以及数字证书 生成控制平面的kubeconfig文件 生成控制平面组件的manifest文件 下载镜像,等待控制平面启动 保存MasterConfiguration 设定Master标志 进行基于TLS的安全引导相关的配置 安装DNS和kube-porxy插件 引导前检查 kubeadm init pre-flight check: kubeadm版本要与安装的kubernetes版本的比对检查 kubernetes安装的系统需求检查 其
禹都一只猫olei
2018/10/22
1.2K0
kubernetes init流程概览
Some Ways To Create An Interactive Shell On Windows
在Twitter看到一种在windows下反弹cmd shell的方法,其实重点不在于反弹shell,而在于这种文件写入的方法可以运用到某些场景中会很方便,比如RCE没有回显时,就可以通过写入一个反弹功能的可执行文件获取到shell,但是你是如何写入该文件的?当然你可能会通过echo一个有下载功能vbs脚本,远程下载该可执行文件,或者powershell,或者bitsadmin,或者certutil,那么这里再介绍给你一种方法将可执行文件直接echo进去! PS:如果小伙伴们还有好的方法可以交流一下! Cr
风流
2018/06/07
5110
推荐阅读
相关推荐
3DSCTF RE Scared Flag
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文