C++vector类

目录

一、vector的使用

1.1、vector的构造,push_back,和 [ ]运算符

1.2、迭代器和范围for

1.3、vector> 和 sort 算法

二、vector的实现

2.1、成员变量

2.2、构造函数,析构函数,赋值重载

​编辑

2.3、push_back,reserve,size,capacity和重载 [] 运算符

2.4、迭代器和范围for

2.5、pop

2.6、insert和erase以及算法库中的find算法


一、vector的使用

1.1、vector的构造,push_back,和 [ ]运算符

从上述图片中可以看出 vector 是使用模版实现的一个容器,我们可以把 vector 看做是一个数组,因为使用了模版,所以 vector 可以存储任意类型的数据,只不过在使用时需要我们自己显式实例化模版,告诉当前 vector 对象存储什么类型的数据。vector 的很多方法使用起来和 string 的很像,所以我们可以类比 string 来学习 vector。

构造函数:

在 vector 的构造函数中,最常用的是无参构造和拷贝构造,使用如图:

push_back 方法:

push_back 方法的作用就是向 vector 对象末尾插入一个数据,使用时只需要像上图代码中那样传入符合存储类型的数据即可。

[ ] 运算符:

这个 [ ] 运算符和 string 类那里的使用起来一样,传入下标,返回该下标对应位置的数据,下标也是从零开始的。前面说过我们可以把 vector 看做是一个数组,这样就很容易理解 [ ] 的用法了。 

使用:

1.2、迭代器和范围for

上面 begin 和 end 方法使用起来和 string 非常相似,但是需要注意接收返回值时的类型写法,vector 的迭代器在 vector 类域里面,所以接收返回值时要指定类域,其次 vector 是用模版写的,所以还要把模版的实例化带上。范围 for 的底层使用的是迭代器,所以支持了迭代器也就支持了范围 for。如图:

vector 也有反向迭代器,这里就不演示了。如图:

vector 使用时的小技巧:假如我们想用 vector 来存储 string 类型的数据。在插入数据时我们可以先创建 string 对象,然后再将该对象存入,也可以直接存放匿名对象,但是这两种方法还是有些麻烦,我们可以利用隐式类型转换,直接存入字符串,它会通过隐式类型转换转换为 string 对象并存储起来。(其他自定义类型,只要支持隐式类型转换都可以这么做)如图:

1.3、vector<vector<类型>> 和 sort 算法

vector<vector<类型>> 在算法题中很常见,我们可以把 vector<vector<类型>> 想象成一个二维数组,这样容易我们理解。如图:

以 vector<vector<int>> 为例,vv 是一个 vector 类型的对象,它的里面存的是一个一个的 vector 容器,而里面的每一个 vector 容器存的是 int 类型的数据,我们可以通过 vv[i][j] 来访问到具体的某一个 int 类型的数据,如图:

上图如果我们使用的是 push_back 方法插入数据就不需要提前更改 size 了,而且 push_back  方法会自动扩容。

sort 算法:

sort 算法不是 vector 的成员方法,这是算法库里面的一个算法,但是可以搭配 vector 在一些算法题中使用,从图中可以看出 sort 算法需要传入一段迭代器区间,它会对这段迭代器区间进行排序,默认是升序,如图:

如果我们需要对一段没有顺序的数据进行降序排列,sort 算法也是可以实现的,sort 算法共重载了两个版本,第一个版本只需要传入一段迭代器区间并排升序,第二个版本除了传入迭代器区间还需要传入一个参数,这个参数叫做仿函数,仿函数并不是一个函数,而是一个类的对象,这个类里面重载了 () 运算符,在这个重载的运算符中编写了比较逻辑,这样就可以通过调这个重载的运算符实现比较并根据比较结果进行排序。C++中有两个写好的仿函数可以在这里使用,一个是greater,一个是 less,通过传入它们,就可以控制升序还是降序,如图:

如果觉得创建仿函数有名对象麻烦,可以直接传匿名对象也可以。

二、vector的实现

2.1、成员变量

首先 vector 是一个可以存储任意类型数据的容器,所以我们需要使用模版来写,其次这里我们仿照库里面的实现来,库里面使用了三个迭代器作为成员变量,其中 _start 指向容器的起始位置(即第一个元素),_finish 指向了最后一个元素的后一个位置,_end_of_storage 指向当前容器总空间的后一个位置,如图:

2.2、构造函数,析构函数,赋值重载

因为使用了模版,所以声明和定义分离会有链接错误,所以我们将所有类的实现的代码写在一个文件中。

无参构造:

无参构造中我们直接将所有的迭代器直接置空就可以了,这里可以显示的写无参构造,也可以直接强制编译器生成默认的,因为后面还会实现别的构造,而一旦有别的构造编译器就不会生成默认构造了,但是我们又有用到无参构造的场景,所以既可以自己写,也可以通过关键字强制生成。如图:

拷贝构造:

拷贝构造只需要遍历 vector 容器,并将里面所有数据通过 push_back 方法插入当前对象中即可,这里可以先提前开好空间,防止 push_back 过程中频繁扩容,影响效率。

用一段迭代器区间进行构造:(也用push_back方法即可)

用 n 个相同元素构造(n有缺省值):

这里实现构造的思路和拷贝构造一样,但是给的缺省值要注意,如果给0,可能 T 是 int 类型,这样就分不清这个0是缺省值还是赋的值,最合适的值就是当前类类型的匿名对象。但是这里还有一个问题,如果使用者传入的 n 是 int 类型,传入的数据也是 int 类型,这时会走迭代器区间的那个构造,对于这个构造,我们期望的是传入一段迭代器区间,但是对于模版而言,它可以是任何数据类型,实际上只要我们传入两个类型相同的参数,并且没有更合适的构造,就会调用这个构造,因为它推导出来的构造更合适,两个类型都是匹配的,所以为了解决这个问题,我们可以实现一个第一个参数是 int 类型的版本,这样有合适的构造就不会在走模版了。如图:

赋值重载:

这里赋值重载采用 string 实现那里的简便方法,直接交换即可。

析构函数:

2.3、push_back,reserve,size,capacity和重载 [] 运算符

因为在插入数据之前我们需要先判断是否需要扩容,所以我们在实现 push_back 方法之前需要先实现 reserve,而扩容又需要用到当前 vector 的容量和有效元素个数,所以我们还需要先实现一个获取容量大小的 capacity 方法,和一个获取有效元素个数的 size 方法。

capacity 方法只需要用指向当前容器总空间的后一个位置的迭代器和指向开头的迭代器相减就可以,因为这里的迭代器是我们用指针封装的,指针相减可以得到中间的元素个数。如图:

size 同理:

reserve 方法的基本思路就是开辟新空间,将旧数据拷贝到新空间中,在释放旧空间并让指向起始位置的迭代器指向新空间,最后在更新 _finish 和 _end_of_storage。这里需要注意的是如果没有提前记录有效数据个数,更新 _finish 采用的是 _start + size() 就会出问题,因为这时 _finish 没有更新还指向旧空间,但是 _start 已经指向新空间了,size() 方法是通过它们相减得到的有效数据个数,但是这时它们相减得到的结果并不正确。

上述代码对于自定义类型还是有一点小问题,比如当前 vector 对象存储的是 string 类型,因为string 并不是直接存储的字符串,string 内部有很多的成员变量,其中有一个用来存储字符串地址的指针,而 memcpy 执行的是浅拷贝,使用该函数,会将这个指针的值直接拷贝下来而不是新开空间存储字符串,这样就造成了多个 string 对象指向同一块空间,这样释放旧空间的时候,这个新申请的空间里面的 string 对象所指向的字符串空间已经被释放了,这时再访问就造成了野指针。如图:

可以这样解决:

这样赋值调用的是 string 对象的赋值重载,而库里面 string 的实现要么是深拷贝,要么是引用计数的写实拷贝,所以不存在浅拷贝的问题。

push_back 只需要先检查是否需要扩容,当确定空间够用后直接放入数据并更新 _finish 就可以了。

重载 [ ] 运算符:先检查传入的参数是否符合允许访问的位置,在将要访问的数据进行返回就可以了。

测试:

2.4、迭代器和范围for

我们直接用指针封装迭代器,其中 const 迭代器只需要用 const 指针就可以了,这样限制解引用,迭代器本身可以进行加减操作也可以读数据,但是无法修改数据。

begin 和 end 方法:begin 方法只需要返回起始位置的迭代器即可(即_start),end 方法返回最后一个元素后一个位置的迭代器(即_finish)。

测试:(支持迭代器后自然就可以使用范围 for 了)

2.5、pop

pop 的实现只需要 --_finish 就可以了,但是在减之前需要先判断一下是否还有数据。

测试:

2.6、insert和erase以及算法库中的find算法

在实现 insert 之前我们先了解一下 find 算法,因为 vector 库里面没有实现 find 方法,所以如果我们想在 vector 对象里面查找某一个数据,需要使用算法库里面提供的 find 算法。如图:

这个方法需要我们传入一段左闭右开的迭代器区间,和一个要查找的值,他会在这段左闭右开的区间里面进行查找,找到返回该元素所在位置的迭代器,找不到返回传入的 last 迭代器。

注意:左闭右开是指 first 迭代器指向位置的值会被查找,last 迭代器指向位置的值不会被查找。

insert实现:我们实现的 insert 有两个参数,一个是要插入位置的迭代器,一个是要插入的数据,还是一样,插入数据之前需要先判断是否需要扩容,但是扩容这里存在一个问题,如果进行扩容了,那么 _start,_finish,_end_of_storage 都会更新指向新的空间,但是 pos 还指向已经被释放的旧空间呢,所以这时 pos 迭代器就无法使用了,我们管这种问题叫做迭代器失效。为了解决迭代器失效,我们可以先记录这个 pos 与 _start 之间的距离,即数据个数,然后在扩容完成后对 pos 迭代器进行更新。之后就可以开始移动旧数据,插入新的数据了,插入完成后还要更新 _finish。但是这里还涉及到一个迭代器失效问题,我们可以从测试中看出来。

测试:

上面测试中问题的答案是不一定,因为在 insert 方法中传迭代器是传值传入,形参的改变不会影响实参,所以虽然方法中更新了 pos 迭代器,但是 it 迭代器并没有更新,所以一旦进行扩容,it 迭代器就会失效,不扩容就不会。库里面也面临这个问题,它的解决办法就是通过返回值,它将更新后的 pos 迭代器返回,接收返回值就能防止迭代器失效了,这里我们也这样写。

erase 实现通过移动数据将要删除的数据覆盖就可以,然后再更新一下 _finish。如图:

测试:

erase 也面临着迭代器失效的问题,我们可以在VS2022上验证一下,如图:

运行结果:

我们可以看到上面代码的运行报错了,这就是因为 erase 这个位置的指针已经失效了,VS的语法检测非常严格,对于访问失效的迭代器直接报错了,这段代码在 Linux 环境下是不会报错的,但是迭代器同样失效了,所以我们统一认为 erase 后的迭代器就是失效的。失效的原因有两个,一个是erase 后是否会缩容不确定,如果缩容了,就和 insert 一样,迭代器失效,另一个是如果删除的是最后一个元素,因为每删除一个元素 _finish 都会向前移动一位,所以删除最后一位后迭代器就指向了 _finish(或者说 _finish 移动到了这个迭代器的位置),这个位置本就无法访问,所以迭代器也失效了。库里面解决这个问题也是通过返回值,但是如果是第二种情况失效,返回迭代器也无法进行访问(即解引用)。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模拟调制技术详解

内容摘要 本文系统讲解模拟调制技术原理及Matlab实现&#xff0c;涵盖幅度调制的四种主要类型&#xff1a;双边带抑制载波调幅&#xff08;DSB-SC&#xff09;、含离散大载波调幅&#xff08;AM&#xff09;、单边带调幅&#xff08;SSB&#xff09;和残留边带调幅&#xff08;…

Android APP 启动流程详解(含冷启动、热启动)

目录 一、流程对比图 二、冷启动&#xff08;Cold Launch&#xff09; 2.1 用户点击应用图标&#xff08;Launcher 触发&#xff09; 2.2 AMS 处理启动请求 2.3 请求 Zygote 创建新进程 2.4 初始化应用进程 2.5 创建 Application 对象 2.6 启动目标 Activity 2.7 执行 …

前端项目中export和import的作用

之前写过代码&#xff0c;但是那个时候是使用jspdivcss写页面&#xff0c;jquery负责页面数据展示和数据请求。近期在学习前端&#xff0c;发现有export和import&#xff0c;想起了之前没用过&#xff0c;就研究搜索了一下&#xff0c;发现这个是在 ES6中添加的&#xff0c;难怪…

玩转ChatGPT:GPT 深入研究功能

一、写在前面 民间总结&#xff1a; 理科看Claude 3.7 Sonnet 文科看DeepSeek-R1 那么&#xff0c;ChatGPT呢&#xff1f; 看Deep Research&#xff08;深入研究&#xff09;功能。 对于科研狗来说&#xff0c;在这个文章爆炸的时代&#xff0c;如何利用AI准确、高效地收…

QLabel 介绍

一、介绍 QLabel 是标签&#xff0c;显示类控件。 二、属性 属性说明text显示的文本textFormat文本格式pixmap设置标签里面的图片scaledContexts内容是否自动填充标签&#xff08;用于图片填满标签&#xff09;alignment对齐方式wordWarp文本是否换行indent设置文本缩进marg…

ubuntu 20.04 C++ 源码编译 cuda版本 opencv4.5.0

前提条件是安装好了cuda和cudnn 点击下载&#xff1a; opencv_contrib4.5.0 opencv 4.5.0 解压重命名后 进入opencv目录&#xff0c;创建build目录 “CUDA_ARCH_BIN ?” 这里要根据显卡查询一下,我的cuda是11&#xff0c;显卡1650&#xff0c;所以是7.5 查询方法1&#xff1…

更新Vim使其支持系统剪切板

参考链接 [转]vim如何复制到系统剪贴板 - biiigwang - 博客园 执行命令 sudo apt-get install vim-gtk 可能遇到的报错 原因 旧版本的系统大多使用vim-gtk&#xff0c;在新版本中已经不存在这个软件包 可以通过输入命令查找是否存在 apt search vim-gtk 可以看到并没有…

TMS320F28P550SJ9学习笔记6:SCI所有寄存器__结构体寄存器方式配置 SCI通信初始化__库函数发送测试

继续学习如何使用结构体寄存器的方式配置这款单片机的外设&#xff0c;这里配置SCI通信的初始化 但SCI gpio 的初始化还是调用的库函数比较方便&#xff0c;它的发送部分页调用了库函数 有关收发方面的逻辑&#xff0c;我会在之后重新自己写一次 文章提供测试代码讲解、完整…

静态时序分析STA——2. 数字单元库-(1)

参考文献 [1]Static Timing Analysis for Nanometer Designs A Practical Approach [2]静态时序分析圣经翻译计划——第三章&#xff1a;标准单元库 &#xff08;上&#xff09; 一. 引脚电容 标准单元库的每个cell的每个输入和输出都可以在pin上指定电容。在大多数情况下&…

Spring-事务

Spring 事务 事务的基本概念 &#x1f539; 什么是事务&#xff1f; 事务是一组数据库操作&#xff0c;它们作为一个整体&#xff0c;要么全部成功&#xff0c;要么全部回滚。 常见的事务场景&#xff1a; 银行转账&#xff08;扣款和存款必须同时成功&#xff09; 订单系统…

蓝桥备赛(13)- 链表和 list(下)

一、动态链表 - list (了解) new 和 delete 是非常耗时的操作 在算法比赛中&#xff0c;一般不会使使用 new 和 delete 去模拟实现一个链表。 而且STL 里面的 list 的底层就是动态实现的双向循环链表&#xff0c;增删会涉及 new 和 delete&#xff0c;效率不高&#xff0c;竞赛…

MySQL中like模糊查询如何优化?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL中like模糊查询如何优化?】面试题。希望对大家有帮助&#xff1b; MySQL中like模糊查询如何优化? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 MySQL 中&#xff0c;LIKE 模糊查询虽然非常常见&#xff0c;…

DeepSeek使用教程--让DeepSeek生成精准题库

想让DeepSeek出好题&#xff0c;关键在于提示词的设计。总结了一个基本模板&#xff1a; 请帮我生成一套关于[学科/知识点]的题目&#xff0c;包括[题型]&#xff0c;难度为[简单/中等/困难]&#xff0c;适合[年级/学习阶段]的学生&#xff0c;总共[数量]道题。每道题请提供详细…

字符串习题

单词个数统计 原作&#xff1a; 输入&#xff1a; 一行字符串。仅有空格和英文字母构成。 输出&#xff1a; 英文字母个数letter_num 单词个数word_num 出现最多的字母max_letter 出现最多的字母的出现次数max_letter_frequ 处理&#xff1a; 统计并输出此句子英文字母…

k8s概念及k8s集群部署(Centos7)

Centos7部署k8s集群 部署之前&#xff0c;先简单说下k8s是个啥&#xff1a; 一、k8s简介&#xff1a; k8s&#xff0c;全称&#xff1a;kubernetes&#xff0c;它可以看作是一个分布式系统支撑平台。k8s的作用&#xff1a; 1、故障自愈&#xff1a; k8s这个玩意可以监控容器…

牵引线标注:让地图信息更清晰的ArcGIS Pro技巧

在地图制作的世界里&#xff0c;标注的清晰度直接决定了地图的可读性和实用性。 今天&#xff0c;就让我们一同探索如何在ArcGIS Pro中巧妙地实现牵引线标注&#xff0c;为地图信息的呈现增添一份专业与清晰。 一、引言&#xff1a;牵引线标注的魅力 在地图制作中&#xff0…

VBA 数据库同一表的当前行与其他行的主键重复判断实现方案

目的&#xff0c;判断是否主键重复&#xff0c;不重复则登录新数据&#xff0c;重复则不登录。 定义类型&#xff1a; DataRecord   tableName 表名   rowNumber 行号   columnName 列名   data 数据 想要实现的代码逻辑如下&#xff1a; 模拟数据库的登录过程。假设…

Qt常用控件之树形QTreeWidget

树形QTreeWidget QTreeWidget 表示一个树形控件&#xff0c;里面的每一个元素&#xff0c;都是一个 QTreeWidgetItem 类型的对象&#xff0c;每个 QTreeWidgetItem 都可以包含多个文本和图标&#xff0c;每个文本或图标为一个列。 需要注意的是&#xff0c; QTreeWidget 向用…

java通用自研接口限流组件

某业务中需要对后端接口进行限流&#xff0c;我们可以直接引入阿里巴巴的Sentinel快速实现&#xff0c;但是某企业中出于安全考虑&#xff0c;需要部门自己研发一套&#xff0c;可以采用RedisLua脚本AOP反射自定义注解来实现 思路来源于链接 项目结构&#xff1a; 启动类&…

小程序事件系统 —— 33 事件传参 - data-*自定义数据

事件传参&#xff1a;在触发事件时&#xff0c;将一些数据作为参数传递给事件处理函数的过程&#xff0c;就是事件传参&#xff1b; 在微信小程序中&#xff0c;我们经常会在组件上添加一些自定义数据&#xff0c;然后在事件处理函数中获取这些自定义数据&#xff0c;从而完成…