当前位置: 首页 > article >正文

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

在这里插入图片描述

文章目录

  • 一、STL简介
    • 1. 什么是STL
    • 2. STL的版本
    • 3. STL的组成
    • 4. 如何学习STL
  • 二、string类的使用
    • 第一部分
    • 第二部分
    • 第三部分
    • 第四部分(含auto与范围for)
    • 第五部分
    • 第六部分
    • 第七部分

一、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类对象,我们写个程序来把两种情况调试看看,如下:

#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(),这里的命名规则和我们之前的命名规则差不多,所以很好懂,它们的作用分别是尾插一个字符与尾删一个字符,不能尾插字符串,我们写一个示例,如下:

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只能尾插一个字符,我们来试着用一下,如下:

 int main()
{
	string s1("hello");
	//尾插一个空格字符
	s1 += ' ';
	//尾插一个字符串
	s1 += "world!";
	cout << s1 << endl;
	return 0;
}

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

在这里插入图片描述

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

在这里插入图片描述

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

//这段代码的运行结果是什么?
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的接口倒是比较好掌握,接下来我们简单写个测试程序练练手,如下:

//下面代码输出的结果是什么?
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的接口设计很相似,我们来看看它们的接口:

在这里插入图片描述

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

//下面代码
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类,如下:

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迭代器是如何定义的,如下:

string::const_iterator it = s1.begin();

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

在这里插入图片描述

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

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,它可以自动帮我们识别类型,让我们不用写这么长的类型名,如下:

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,它的作用是给我们提供一个快捷方便的遍历方式,如下:

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()是清空容器,接下来我们先把这四个函数测试一下,如下:

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,我们这里介绍的第三种就是利用数组的方式进行遍历,如下:

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的作用是扩容,如果我们传过去的参数小于当前容器的容量,那么它什么也不会做,只有当我们传过去的参数大于当前容器的容量时才会进行扩容,并且扩容时的逻辑是至少扩容到那个容量,可能会多几到十几的容量,我们写个代码来用于调试,如下:

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个元素,我们写个程序验证一下,如下:

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,如果使用第二个接口,那么中间填充的就是我们传过去的那个字符,我们写个程序证明,如下:

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,如果是第二个接口传了字符,那么就全部填充这些字符,我们来实践一下,如下:

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风格的字符串函数无缝地互操作,接下来我们来测试一下,如下:

//等价于#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我们在前面介绍过,就是一个非常大的静态成员变量,接下来我们举个具体的例子,如下:

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,其实就是截取整个字符串,也可以自行指定开始和长度,我们来尝试用一用,如下:

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,如下:

在这里插入图片描述

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~


http://www.kler.cn/a/582903.html

相关文章:

  • 【2025】基于springboot+vue的汽车销售试驾平台(源码、万字文档、图文修改、调试答疑)
  • 前:vue 后:django 部署:supervisor+nginx 流程及部分问题简记
  • python编写的一个打砖块小游戏
  • 基于AI智能算法的无人机城市综合治理
  • 计算机操作系统(一) 什么是操作系统
  • 安卓应用架构模式 MVC MVP MVVM有什么区别?
  • 多云环境中的大数据部署:从挑战到最佳实践
  • Vscode工具开发Vue+ts项目时vue文件ts语法报错-红波浪线等
  • 关于Java的入门
  • 解锁 Postman:下载安装与账户注册使用的全攻略,踏上测试新征程
  • Java后端高频面经——计算机网络
  • hadoop第3课(hdfs shell常用命令)
  • word排版:段内公式由于固定行间距显露不出的问题
  • kafka + flink +mysql 案例
  • Linux NFS/TFTP文件系统挂载
  • C# 数据类型总结
  • Android ANR 监控方法与事件分发耗时优化实战
  • 无人机全景应用解析与技术演进趋势
  • 新能源汽车电控系统的大尺寸PCB需求:猎板PCB的技术突围
  • 【后端开发面试题】每日 3 题(十)