Learning C++ No.10【STL No.2】

news2024/9/29 23:34:00

引言:

北京时间:2023/2/14/23:18,放假两个月,没有锻炼,今天去跑了几圈,一个字,累,感觉人都要原地升天了,所以各位小伙伴,准确的说是各位卷王,一定要坚持锻炼,不敢像我一样哦!身体才是本钱,锻炼才是王道,并且今天看了一下小黑书和计算机导论中的一些有关计算机系统的知识,发现以前通过别人把饭嚼碎给我们吃获得知识的方式,在碰到一个很陌生的知识点的时候,只能通过强硬的记忆理解去理解这个知识点,导致我们不能很好的理解这个知识点,所以虽然通过人家把饭嚼碎给我们吃,这样能够更容易上道,但是会导致不易学透;所以当自己去看书的时候,去理解书中讲解的知识点的时候,我充分发现,还是一定要自己学会吃饭,不能一直吃别人嚼碎的饭,这样才可以更好的理解某些知识点;本今天是打算自己开一个专栏,记录一下今天看到的几个有关计算机系统的,比较容易理解不全面的,当然相对于我自己来说,是和自己以前的理解有所偏差的知识点,但是由于时间问题,所以今天并没有记录,明天咱们再搞这一块。现在让我们再次进入STL的世界,开始string类的二次深入学习吧!
在这里插入图片描述

再谈string类

string类其实就是一个管理字符串的类,里面的函数就是用来对字符进行增删查改用的,用来像顺序表一样,管理一个字符数组;在上篇博客中,我们已经把string类中的一些接口给学习过了,现在我们就继续学习一下别的string类中的接口,首先是接着上篇博客,学习一下有关容量的接口。
首先我们就先来看一下容量函数中的capacity接口,观察一下它是怎么进行扩容的,如图:

我们可以发现,在vs中capacity函数大致是以1.5倍进行扩容的,而在Linux中,capacity函数大致是以2倍进行扩容的,所以我们可以得出结论,在不同的编译器上,STL库是有一定的区别的,所以导致string类中的capacity函数是有一定的区别的,所以导致扩容的倍数是有一定区别的。

讲述上述capacity函数的扩容原理,本质上我们是想要为了解string类中的reverse函数做一些铺垫的,reserve函数的作用就是可以直接进行开空间,当知道需要多少空间时,可以直接提前开空间,把空间开好之后,就是可以减少扩容,因为扩容是有消耗的,所以可以提高程序的效率。前提是知道需要多少空间哦!如下图就是reverse函数开空间的使用方法:

可以看出在编译器不同的情况下,使用reserve函数直接开空间,也是存在着一定的差异的,但是一定要区分reverse函数(逆置函数)。搞定了reserve函数,此时我们再来看一看什么是resize函数,

在这里插入图片描述

如图我们可以发现resize函数有两种用法,一种是开辟空间,和更改size的大小,一种是可以给一个字符,把剩下的size空间全部填充该字符,如上图中所示,并且此时从另一个层面去看,resize函数还具有删除数据的功能(原理:如果此时的容量比resize开的小,那么就增大空间,如果比resize开的大,那么就减小空间),此时的减小空间就可以理解为此时的删除数据,例:如图

如图可以发现,此时的size确实是变成了5,然后只剩下了hello,把后面的内容都给删除了,也可以发现capacity容量是没有改变的。所以本质上resize就是在对string类中的成员变量中指针指向的那块空间的数组进行改变而已。

浅浅摸一下迭代器

我们可以知道迭代器是STL中的一个重点知识,所以此时我们就先来浅浅的摸一下它,为以后迭代器的学习打下一定的基础。
如图就是迭代去在string中使用的一个经典代码:

正序遍历

这个就是 string类 中迭代器的基本函数:

综合上述的两幅图,可以发现,begin就是迭代器的开始位置,end就是迭代器的结束位置,上述代码中,我们使用这两个迭代器函数就可以很好的遍历一个字符串,但是要注意,在使用begin和end的时候,我们最终得到的还是该位置的地址而已,所以想要获取其中的数据,就一定需要进行解引用操作,并且目前我们可以把迭代器想象成C语言中的指针,但是以后就不行了,因为后面的迭代器会更加的复杂,我们这里只是简单的摸一下它而已, 但是在底层的代码实现,本质上迭代器肯定还是使用指针实现。并且当我们学了迭代器,我们就可以使用三种不同的方式去访问string范围for、迭代器、下标+[] ,但是 下标+[]的本质上是使用了[]运算符重载范围for本质上是使用了迭代器,足以看出迭代的重要性,STL六巨头之一不是徒有虚名的。

逆序遍历

并且可以看出,上述我们对数组的遍历是从前向后遍历的,所以该迭代器称之为正向迭代器,接下来我们就介绍一下什么是反向迭代器

注意: 因为此时是反向迭代器,所以rbegin是在rend的后面的,所以此时rbegin想要靠近rend也是要使用加加,不可以使用减减

总:正向迭代器,加加表示向前走,反向迭代器,加加表示向后走。

了解了什么是正向迭代器,什么是反向迭代器,此时我们就根据迭代器的类型再来聊一聊,普通迭代器和const修饰的迭代器, 如下图:

此时我们可以发现,无论是正向迭代器,还是反向迭代器,它们都具有两种类型,一种是普通的iterator、reverse_iterator类型,另一种是const_iterator、const_reverse_iterator类型,此时的普通类型迭代器是允许遍历和读写容器的数据的而const修饰的迭代器只允许遍历和读,并不允许写

如下代码就是这4中类型的迭代器:

#include<iostream>
#include<string>
using namespace std;

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();//正向迭代器
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	string::reverse_iterator rit = s1.rbegin();//反向迭代器
	while (rit != s1.rend())
	{
		cout << *rit << endl;
		++rit;
	}
	cout << endl;

	string::const_iterator it = s1.begin();//const正向迭代器
	while (rit != s1.rend())
	{
		cout << *it << endl;
		++rit;
	}
	cout << endl;

	string::const_reverse_iterator rit = s1.rbegin();//const反向迭代器
	while (rit != s1.rend())
	{
		cout << *rit << endl;
		++rit;
	}
	cout << endl;

	return 0;
}

并且此时可以发现,迭代器的类型的返回值类型是比较长的,所以此时我们就可以使用以前学的一个自动匹配类型的关键字auto ,使用auto的好处就凸显出来了,减少代码量,所以auto在有些场景中是非常的实用的,不过前提是我们自己知道这个函数的返回值类型具体是什么,这样使用auto才可以让我们更加遍历,但是auto也是有一定的缺点的,就是导致代码的可读性很低,只有了解这部分知识的人,才可以看懂代码,如下图:就是auto和迭代器的结合使用。

注意: 此时const类型的迭代器中的const修饰的是数据不能被修改,并不是位置不能被修改,所以此时rit是可以修改的,*rit是不可以修改的。

为什么要学习迭代器

很多同学可能会有疑问,我们可以使用下标和重载运算符[]来实现字符串的遍历,那么我们为什么还要学习迭代器呢?不都是为了遍历字符串吗?原因:链表和树状结构,如果使用**重载运算符operator[]**的效率是非常的低下,所以就不可以使用[]运算符和下标进行重载,此时就只可以只能使用迭代器,只有使用迭代器才可以让遍历其它非数组结构的数据结构的效率更高 ,所以C++中迭代器的概念是为了给除了数组以外的数据结构使用的。

总:迭代器作为STL中的六巨头之一,不是浪得虚名的,非常重要!

string类中的各种接口

string类中的insert函数

如图insert函数就是对字符串进行任意位置的插入字符或者字符串而已,并且也可以使用迭代器的形式就行任意位置的插入,并且insert函数的重载函数是非常多种的,可以说是满足任何的插入需求,但是注意: 因为insert每次插入字符都是以移动字符为前提的,所以在string类中,我们应该少使用insert函数,这样可以提高我们代码的效率。

string类中的erase函数

上述最后一个使用样例是属于迭代区间,目前我们就先不做了解,以后再了解,并且此时的erase函数也是和insert一样,我们并不支持经常使用,原理:经常挪动数据效率低下。

string类中的replace函数

该函数的使用是非常的不友好的,因为它不仅需要进行扩容,还需要进行字符的挪动,只有这样才可以实现该函数,所以效率是非常的低下的。

string类中的find函数

学习find函数,此时我们通过一个题目来搞定。

题目:把一句英文中的空格全部给替换成目标字符串。

代码如下:

我们使用了find函数的第一个样例,在特定的位置开始找目标字符,并且此时有一个缺省值(0),如果不是特定位置,它就会默认从0开始找目标字符,但是我们为了提高程序的效率,我们可以把这个值给成pos位置的后一个位置或者后几个位置,目的就是为了让find函数,不用每次都从头开始找,而是直接跳过我插入的特定字符,从特定字符的后一个位置去找,这样每次都是在向后遍历,极大的提高了程序的时间复杂度,并且如代码中,我们还使用了reserve函数进行开空间,前提是我们知道此时需要多少的空间,这样就可以避免扩容带来的消耗,极大的提高程序的效率。

并且此时该题还有第二种写法,一种用空间换时间的写法,但是代码的缺陷和上述代码还是差不多的,都是可以使用提前开空间(reserve)函数来进行优化的,如下代码:

#include<iostream>
#include<string>
using namespace std;

int main()
{
	string str("hello world i love you");
	string newStr;
	size_t num = 0;
	for (auto ch : str)
	{
		if (ch == ' ')
		{
			++num;;
		}
	}
	newStr.reserve(str.size() + 2 * num);//还是提前把空间开好
	for (auto ch : str)
	{
		if (ch != ' ')
		{
			newStr += ch;
		}
		else
		{
			newStr += "20%";
		}
	}

	str = newStr;
	cout << str << endl;

	return 0;
}

从这个题目,我们可以发现,在string类中是有非常多好用的功能(通过各种函数接口),使我们做题变得更加的简洁和灵活,只要你把string类中的函数给熟练的使用,是真的可以很好的利用它们去解决各种有关字符的题目。

利用string类中的函数解决问题

搞定了find函数,我们把string类中的常见的函数就给搞定的差不多了,此时我们就可以使用这些函数来做一些题目了,如下题:

给你一段有运算符和英文字母的语句,然后仅反转其中的英文字母,非英文字母保留在原有位置,所有英文字母(小写或大写)位置反转,然后返回反转后的 s
例:
输入:s = “ab - cd”
输出:“dc - ba”

并且此时通过这个题目,有一个注意点,就是if和if的使用和if和else if的使用

1.从字面上理解if为如果,就是如果这种情况,如果那种情况。
2.else if 不是上一个条件的前提下,如果是这个条件。(总:else if是在上一个条件不成立的情况下,进行判断)
3.区别1:if无论是否满足条件都会向下执行,直到程序结束,else if 满足一个条件就会停止执行。(总:else if 一但满足之后,就会停止程序)
4.区别2:由于if都会执行一遍,则可能会同一个需要判断的事件,会进入2个if语句中,出现错误,而else if就不会发生这样的事情。

弄清楚了if、if使用和if、else if的使用,此时我们就正式进入题目:
思路:

  1. 当我们想要去遍历该语句的时候,思考使用那种遍历的方式最合适,发现范围for和迭代器不怎么合适,所以使用下标+[]
  2. 实现判断是否是英文字母的函数,并且注意:前后的开始位置
  3. 前后同时遍历,找字母,是字母就停下来,然后交换,不是字母就加加到后面一个,或者减减到前面一个


#include<iostream>
#include<string>
using namespace std;

class Solution
{
public:
	bool isLetter(char ch)
	{
		if (ch >= 'a' && ch <= 'z')
		{
			return true;
		}
		if (ch >= 'A' && ch <= 'Z')
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	string reverseOnlyLetters(string s)
	{
		size_t begin = 0;
		size_t end = s.size() - 1;
		while (begin < end)
		{
			while (begin < end && !isLetter(s[begin]))
			{
				++begin;
			}
			while (begin < end && !isLetter(s[end]))
			{
				++end;
			}
			swap(s[begin], s[end]);
			begin++;
			end--;
		}

		return s;
	}
};

有了上述的思路和string类中的各种函数接口的使用,我们很愉快的就搞定了该题,所以下一题吧!!!
如题:

字符串中的第一个唯一字符,给定一个字符串s,找到它的第一个不重复字符,并返回它的索引,如果不存在,则返回-1(false)
例:
输入:s = “leetcode”
输出:1

原理:

  1. 该题第一时间,就可以想到使用计数排序的思想,使用映射的方式进行是最好的方法
  2. 按照计数排序的思想,此时第一个步骤就是统计每个字母出现的次数
  3. 判断谁是只出现一次,并且是第一个出现的(通过字符串和映射的数组中统计的次数,直接和1比较就行了)
#include<iostream>
#include<string>
using namespace std;

class Solution
{
public:
	int firstUniqChar(string s)
	{
		int countA[26] = { 0 };//将该数组初始化为0,方便映射,不需要我们自己去初始化了,以前计数排序的时候使用的是memset初始化
		for (auto ch : s)
		{
			countA[ch - 'a']++;//按照原理:下标0放a,1放b,2放c,3放d,所以此时按照这个下标理论,就可以让ch中的某个字母-掉a的ASCII码值,这样就可以在相应的下标位置进行++了
		}

		for (int i = 0; i < s.size(); ++i)//此时这个遍历(因为要知道下标,所以不适合使用范围for和迭代器,使用下标是最合适的)
		{
			//总:遍历,一定要灵活选择
			if (countA[s[i] - 'a'] == 1)
			{
				return i;
			}
		}
	}
};

通过上题,我们可以发现计数排序的好处,但是也可以发现,我们有时候,很难去使用,特别是countA[ch - 'a']++; 统计每一个字符出现的次数的这步,有时候需要我们仔细斟酌,还有就是这步 if (countA[s[i] - 'a'] == 1),让字符串中的字符从前向后去和统计出的出现次数进行比较,看谁是第一个出现一次的,这步也是需要我们细细斟酌,所以这两步一定要熟练的使用,这样才可以让我们的代码更加的优,并且思路更加的清晰,做题更加的轻松。

总:计数排序的思想是非常的好用的,并且string的使用场景(遍历)大部分都是:下标+[]配合使用的。

在这里插入图片描述

总:搞定了string类中常用函数和如何使用,不仅可以为以后STL的学习提供便利,而且可以让我们做题变得更加的轻松。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

与其被行业内卷,还不如主动出击,打破困境~

如今的 “互联行业内卷”这事好像成为了一种常态&#xff0c;尤其是在一些已处于饱和状态和即将处于饱和状态的行业比较突出&#xff0c;比如&#xff1a;Android 开发行业、前端、……等等 造成行业内卷原因是什么&#xff1f; 市场竞争加剧&#xff0c;企业更加注重成本控制…

大数据---Hadoop集群搭建

Hadoop集群搭建 再起启动一台虚拟机并且安装jdk&#xff0c;开启免密登录 不需要安装zookeeper 文章目录Hadoop集群搭建时间同步4台机器安装npdate设置定时任务集群配置图将Hadoop安装包上传到zk1zk1---解压到soft目录下zk1---更名zk1---修改配置文件core-site.xmlhdfs-site.x…

第四届宁波网安市赛训练题

Crypto 散乱的密文 8fd4a4c94gf15{50}l72d3提示了2 1 6 5 3 4&#xff0c;我们直接横向排列 2165348fd4a4c94gf15{50}l72d3 按顺序竖着抄下来fc1l84f}a45dg034{2d957,然后栅栏解密&#xff0c;注意这里是W型栅栏解密&#xff0c;行数6 flag:flag{52048c453d794df1} 综合解密…

stm32f429FMC外设学习

功能框图 这是一种型号为 W9825G6KH 的 SDRAM 芯片内部结构框图&#xff0c;以它为模型进行学习 CLK -- FMC_SDCLK //同步时钟信号 CKE -- FMC_SDCKE[1:0] //SDCKE0&#xff1a; SDRAM 存储区域 1 时钟使能,;SDCKE1&#xff1a; SDRAM 存储区域 2 时钟使能.这里表示的…

QT(16)- QFileDevice

QT&#xff08;16&#xff09;- QFileDevice1 简介2 公有类型2.1 enum QFileDevice::FileError2.2 enum QFileDevice&#xff1a;&#xff1a;FileHandleFlag2.3 enum QFileDevice::FileTime2.4 enum QFileDevice&#xff1a;&#xff1a;MemoryMapFlags2.5 enum QFileDevice::…

【OJ】计数的梦

&#x1f4da;Description: Bessie 处于半梦半醒的状态。过了一会儿&#xff0c;她意识到她好像在数羊&#xff0c;不能入睡。Bessie的大脑反应灵敏&#xff0c;仿佛真实地看到了她数过的一个又一个数。她开始注意每一个数码&#xff1a;每一个数码在计数的过程中出现过多少次…

华为OD机试 - 箱子之形摆放(Python)| 真题+思路+考点+代码+岗位

箱子之形摆放 题目 有一批箱子(形式为字符串,设为str), 要求将这批箱子按从上到下以之字形的顺序摆放在宽度为 n 的空地,请输出箱子的摆放位置。 例如:箱子ABCDEFG,空地宽度为3,摆放结果如图: 则输出结果为: AFG BE CD 输入 输入一行字符串,通过空格分隔,前面部…

关于tensorboard --logdir=logs的报错解决办法记录

我在运行tensorboard --logdirlogs时&#xff0c;产生了如下的报错&#xff0c;找遍全网后&#xff0c;解决办法如下 先卸载 pip uninstall tensorboard再安装 pip install tensorboard最后出现如下报错 Traceback (most recent call last): File “d:\newanaconda\envs\imo…

华为3面,官网显示面试通过了...开始泡池子,进入漫长等待期

背景&#xff1a; 现在双非本科&#xff0c;非计算机科班&#xff0c;有算法方面的奖&#xff0c;有嵌入式开发经历&#xff0c;官网显示面试通过&#xff0c;短信说录用情况在十个工作日内告知&#xff0c;看别人的说法应该是泡池子了。 全程视频面试&#xff0c;一天面完三…

[ Azure - IAM ] Azure 中的基于角色的访问控制 (RBAC) 与基于属性的访问控制 (ABAC)

在任何公司中&#xff0c;网络用户必须先经过身份验证和授权&#xff0c;然后才能访问可能导致安全漏洞的系统部分。获得授权的过程称为访问控制。在本文中&#xff0c;我将讨论管理系统访问控制的两种主要方法——基于角色的访问控制 (RBAC)和基于属性的访问控制 (ABAC) &…

春招Leetcode刷题日记-D3-双指针算法-归并两个有序数组Two Sum

D3-双指针算法-归并两个有序数组&&Two Sum什么是双指针算法力扣88. 合并两个有序数组思路代码力扣167. 两数之和 II - 输入有序数组思路思路一&#xff1a;时间复杂度O(nlogn&#xff09;算法代码思路二&#xff1a;时间复杂度O(n)--双指针算法代码什么是双指针算法 1…

c++学习笔记-案例-职工管理系统(哔站-黑马程序员c++教学视频)

目录 一、需求分析 1、职工管理系统需求 2、管理系统中需要实现的功能 3、系统界面如下&#xff1a; 二、代码实现 1、整体结构 2、具体分析 &#xff08;1&#xff09;创建01workerManager.h &#xff08;2&#xff09;创建02worker.h &#xff08;3&#xff09;创建…

秒杀项目的消息推送

目录 一、创建消费者 二、创建订单链路配置 1.定义RabbitMQ配置类 2.创建RabbitmqOrderConfig配置类 三、如何实现RabbitMQ重复投递机制 1.开启发送者消息确认模式 2.消息发送确认 ① 创建ConfirmCallBacker确认模式 ② 创建ReturnCallBack退回模式 3.创建生产者 …

*1创建 Vue3

1、使用 vue-cli 进行创建 &#xff08;1&#xff09;查看 vue-cli 版本,确保 vue-cli 版本在4.5.0上 win键 r &#xff0c;输入cmd ——>输入 vue -V 若低于4.5.0版本&#xff0c;则需要重新安装 npm i -g vue/cli &#xff08;2&#xff09;在桌面创建项目&#xff08;也…

天荒地老修仙功-第七部:Dubbo基本使用与原理详解

文章目录一、基础知识1、分布式基础理论1.1、什么是分布式系统&#xff1f;1.2、发展演变1.2.1 单一应用架构1.2.2 垂直应用架构1.2.4 分布式服务架构1.2.4 流动计算架构1.3、RPC2、dubbo核心概念2.1、简介2.2、基本概念2.2.1 服务提供者&#xff08;Provider&#xff09;2.2.2…

ViewBinding使用入门

ViewBinding 参考资料: 新技术 ViewBinding 最佳实践 & 原理击穿 更多 ViewBinding 的封装思路 1. kotlin-android-extensions(KAE) 的问题 根据Google官方的说法, KAE存在以下问题: 污染全局命名空间不能暴露可空性信息仅支持Kotlin代码 kotlin在1.4.20中 开始废弃这…

广州暨大港澳子弟学校小学部IB探究

作为IB世界会员学校的一员&#xff0c;培养学生成为具有国际情怀的人&#xff0c;承认人类共有的博爱精神&#xff0c;分担守护地球的责任&#xff0c;帮助开创一个更好、更和平的世界而努力是广州暨大港澳子弟学校&#xff08;ASJ&#xff09;的教育使命和目标。 ASJ在PYP项目…

使用Junit框架,提高自动化测试效率

目录 1、Junit 是什么&#xff1f; 2、Junit 的用法 2.1、注解【Test、BeforeEach、BeforeAll、AfterEach、AfterAll】 2.2、断言Assertion类 2.2.1、断言匹配/不匹配 2.2.2、断言结果为真/为假 2.2.3、断言结果为空/不为空 2.3、用例的执行顺序【order注解】 2.4、参…

字节跳动青训营--前端day10

文章目录前言一、web开发安全 - 攻击1. Cross-Site Scripting(XSS)1.1 存储型&#xff08;Stored Xss&#xff09;1.2 反射型&#xff08;Reflect Xss&#xff09;1.3 DOM型&#xff08;DOM Xss&#xff09;1.4 Mutation-based XSS2. Cross-Site Request Forgery&#xff08;CS…

如何使用TypeScript封装一个简单好用的Http工具

前言 Http 请求对于任何系统都是一大基石&#xff0c;那么如何封装一个高可用的 Http 请求工具呢&#xff1f;接下来手把手教你使用 TypeScript 封装一个高可用的 Http 请求工具。 本工具由三部分构成&#xff1a;Http基础层、基础方法层、业务层。 Http基础层 Http基础层主…