vector你得知道的知识

news/2024/5/15 3:44:01/文章来源:https://blog.csdn.net/weixin_54209978/article/details/129426253

vector的基本使用和模拟实现

在这里插入图片描述

一、std::vector基本介绍

1.1 常用接口说明

在这里插入图片描述

std::vector是STL中的一个动态数组容器,它可以自动调整大小,支持在数组末尾快速添加和删除元素,还支持随机访问元素

以下是std::vector常用的接口及其说明:

  1. push_back(): 在容器末尾添加元素
std::vector<int> vec{1, 2, 3};
vec.push_back(4);
  1. pop_back(): 删除容器末尾的元素
std::vector<int> vec{1, 2, 3};
vec.pop_back();
  1. size(): 返回容器中元素的个数
std::vector<int> vec{1, 2, 3};
std::cout << vec.size() << std::endl; //输出3
  1. empty(): 判断容器是否为空

  2. reserve(): 分配容器的内部存储空间,但不改变元素个数

std::vector<int> vec{1, 2, 3};
vec.reserve(10);	// 分配10个int大小的空间

其中的reserve接口,你说是分配容器空间,这是在堆上还是栈上开辟空间?那如果重新分配的空间比现有空间小,会发生什么?以及重新分配的空间大于现有空间,是在现有的基础上直接扩容,还是舍弃现有空间,将现有数据拷贝到新的空间上?

新分配的内存空间位于堆上。如果重新分配的空间比现有空间小,std::vector 会舍弃多余的元素。如果新分配的空间比现有空间大,std::vector 会重新分配内存,并将原有数据复制到新的内存空间中。

需要注意的是,重新分配内存并将原有数据复制到新的内存空间中,可能会导致性能问题。因此,如果您能够预估存储的数据量,建议在创建 std::vector 时就预分配足够的内存空间,以避免频繁地重新分配内存。

  1. resize(): 改变容器的元素个数
std::vector<int> vec{1, 2, 3};
vec.resize(5);
  1. clear(): 删除容器中的所有元素

  2. at(): 返回指定位置的元素

std::vector<int> vec{1, 2, 3};
std::cout << vec.at(1) << std::endl; //输出2
  1. front(): 返回容器中第一个元素

  2. back(): 返回容器中最后一个元素

  3. begin(): 返回指向容器中第一个元素的迭代器,end(): 返回指向容器中最后一个元素之后位置的迭代器

std::vector<int> vec{1, 2, 3};
for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";
}
  1. reverse():反转vector

std::vector::reverse 不是重新分配容器空间的接口,它是用于反转容器中元素的顺序。也就是将容器中第一个元素和最后一个元素交换,第二个元素和倒数第二个元素交换,以此类推。

1.2 代码示例

在这里插入图片描述

1.2.1 遍历vector的几种方式

以下示例中分别提到了:下标+[], 迭代器, 范围for, 反向迭代器

void test_vector1()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i = 0; i < v.size(); i++)		//1、下标+[]{cout << v[i] << " ";}vector<int>::iterator it = v.begin();		//2、迭代器while (it != v.end()){cout << *it << " ";it++;}for (auto e : v)			//3、范围for{cout << e << " ";}vector<int>::reverse_iterator rit = v.rbegin();	// 4、反向迭代器while (rit != v.rend()){cout << *rit << " ";rit++;}// 5 4 3 2 1
}

不难发现,用范围for访问的元素可以直接输出,而通过for循环,从v.begin()开始的迭代器it,却需要解引用。这是因为,在范围for循环中,迭代器是被自动解引用的。

在范围for循环中,循环变量的类型是根据容器元素的类型自动推导出来的,而不是容器的迭代器类型。对于std::vector<int>容器,其元素类型是int,因此在范围for循环中,循环变量的类型是int,而不是std::vector<int>::iterator

1.2.2 使用迭代器区间构造对象

vector<int> v2(++v.begin(), --v.end());		//利用迭代器区间构造对象————区间左闭右开 
// 2 3 4
string s("hello world");
vector<char> v3(s.begin(), s.end());		//其它容器的迭代器只要类型匹配同样适用vector<int> v4;
v4.assign(s.begin(), s.end());				//assign接口类似————中文意思为分配

以上示例中,提到了利用迭代器区间构建对象这种方式是左闭右开的,例如:(v.begin() + 1, v.begin() + 4),这代表下标为[2, 5)的元素。

只要迭代器的类型与vector所存储的元素类型相同,就可以使用迭代器区间构建新的vector对象。例如上面提到的char类型的vector和char类型的string是可以匹配的。

在这里插入图片描述

1.2.3 vector的初始化

void test_vector2()
{vector<int> v;v.reserve(10);//开空间改变容量,但不初始化//错误访问——————下标引用操作符会检查插入位置是否合法,即小于_size//for (size_t i = 0; i < 10; i++)//{//	v[i] = i;//}//正确访问for (size_t i = 0; i < 10; i++){v.push_back(i);}v.resize(20);//开空间+初始化
}

上述示例中,提到了我在OJ题中经常弄糊涂的内容,使用reserve进行初始化是最好的,因为不会像resize那样,擅自向vector中填入值,但使用后记得不要使用下标引用操作符去访问,而是使用push_back

1.2.4 insert\查找\排序

void test_vector3()
{int a[] = { 1,2,3,4,5 };vector<int> v(a, a + 5);//头插v.insert(v.begin(), 0);			//第一个参数传入的是迭代器//在2前面插入vector<int>::iterator pos = find(v.begin(), v.end(), 2);		//find函数位于算法库中algorithmif (pos != v.end())		//查找失败会返回end位置的迭代器{v.insert(pos, 20);}// 0 1 20 2 3 4 5  //sort排序sort(v.begin(), v.end());	// 0 1 2 3 4 5 20 sort(v.begin(), v.end(), greater<int>());		//greater<int>是一个仿函数类,需要调用库函数是functional// 20 5 4 3 2 1 0
}

上述例子注释写得很清楚,印象最深的是二叉树后序遍历可以巧用头插获取返回结果。下面主要讲解sort的第三个参数:

sort函数的第三个参数是可选的比较函数,用于指定排序时的元素比较规则。当不指定比较函数时,默认使用小于号进行比较,即升序。

sort(v.begin(), v.end(), greater<int>())中,greater<int>()是一个函数对象,用于指定降序排序的比较规则。greater<int>()是一个模板类,它重载了函数调用运算符operator(),实现了比较规则。对于两个元素x和y,如果greater<int>()(x, y)返回true,则x会被排在y之前。

因此,sort(v.begin(), v.end(), greater<int>())实现了对容器v进行降序排序的操作,greater<int>()是用于指定比较规则的函数对象,它实现了元素的比较运算。

下面是一个简单的实现:

template<typename T>
struct greater
{bool operator()(const T& x, const T& y) const{return x > y;}
};

这个定义了一个模板类greater,它有一个函数调用运算符operator()operator()接受两个参数xy,表示要比较的两个元素,它的返回值是一个bool类型,表示x是否应该排在y之前。在这个实现中,operator()的比较规则是x大于y,即从大到小排序。

1.2.5 erase删除

void test_vector4()
{int a[] = { 1,2,3,4,5 };vector<int> v(a, a + 5);//头删v.erase(v.begin());		//参数传入下标位置的迭代器,或迭代器区间//删除2vector<int>::iterator pos = find(v.begin(), v.end(), 2);if (pos != v.end()){v.erase(pos);}
}

上述例子中主要写了erase的使用方式,参数是一个vector的迭代器。

二、vector模拟实现

在这里插入图片描述

这个 vector 类中包含了:构造函数、拷贝构造、使用迭代器区间初始化的构造函数、析构。

实现了普通迭代器和只读迭代器,及其对应的begin()end()函数。

这个类中的成员变量为私有类型的普通迭代器类型的。记录了vector开始位置、最后一个数据的下一个位置、最大容量的下一个位置。

size()capacity()能返回容器的元素个数和已开辟空间大小。

reserve()用于为容器重新分配空间,如果分配的空间小于现有空间容量,则不处理。否则,会重新分配空间,拷贝现有数据到新空间,并修改成员变量。

insert()用于向容器中插入元素,插入时需要将所在位置及其以后的元素全部向后移动,移动时是从后往前。

erase()用于删除某个元素,参数为要删除元素的迭代器。删除后还需要从前往后开始,逐一将元素向前移动。

resize()用于调整空间大小,并将未初始化的位置赋予指定的值。当调整的空间小于现有元素个数,会舍弃掉多出的元素。当调整的大小介于元素个数和有效空间之间时,会初始化这些多出来的部分。当调整的大小大于有效空间时,会重新开辟空间,并将现有数据拷贝过去,然后初始化多余部分。

其他接口比较简单,就不再单独描述。

	template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (const auto e : v) {push_back(e);}}template <class InputIterator>vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last) {push_back(*first);first++;}}~vector() {delete[] _start;_start = _finish = _endofstorage = nullptr;}iterator begin() {return _start;}iterator end() {return _finish;}const_iterator begin()const {return _start;}const_iterator end()const {return _finish;}size_t capacity() const{return _endofstorage - _start;}size_t size() const{return _finish - _start;}void reserve(size_t num) {if (num > capacity()) {size_t sz = size();T* tmp = new T[num];memcpy(tmp, _start, sz * sizeof(T));_start = tmp;_finish = _start + sz;_endofstorage = _start + num;}}iterator insert(iterator pos, const T& num){assert(pos >= begin() && pos <= end());if (_finish == _endofstorage) {size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator end = _finish - 1;while (end >= pos) {*(end + 1) = *end;end--;}*pos = num;_finish++;return pos;}iterator erase(iterator pos) {assert(pos >= begin() && pos < end());//删除指定下标的数据,并把其后的数据依次向前挪动iterator it = pos + 1;while (it != end()){*(it - 1) = *it;it++;}--_finish;return pos;}void push_back(const T& num){insert(end(), num);}T& operator[](size_t i) {assert(i < size());return *(_start + i);}void swap(vector<T>& v) {std::swap(v._start, _start);std::swap(v._finish, _finish);std::swap(v._endofstorage, _endofstorage);}vector<T>& operator=(vector<T> v) {swap(v);return *this;}void resize(size_t n, const T& val = T()) {//开的空间小于size(把超出范围的舍弃)介于size和capacity(初始化_finish以后的空间)//大于capacity(要重新开空间,并且初始化_finish以后的空间)if (n <= size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish < _start + n) {*_finish = val;_finish++;}}}private:iterator _start;iterator _finish;iterator _endofstorage;};

在这里插入图片描述

三、reverse浅拷贝Bug

不难发现,在上述模拟实现的vector::reverse()中,如果模版类型T为std::string,那string中含有一些指针成员变量,通过memcpy将其浅拷贝到新空间后,又进行了一次delete,在生命周期结束时,也会进行delete,就会造成崩溃。

3.1 解决方案

既然要销毁原有空间,那为何不通过std::move将左值转换为右值,然后拷贝到新空间去。

下面通过伪代码进行举例:

template <typename T>
void MyVector<T>::reserve(size_t newCapacity)
{if (newCapacity <= m_capacity)return;T* newData = new T[newCapacity];for (size_t i = 0; i < m_size; i++){newData[i] = std::move(m_data[i]); // 深复制}delete[] m_data;m_data = newData;m_capacity = newCapacity;
}

上述代码没有考虑使用迭代器成员变量,容量和有效数据数量均为size_t类型,数据类型为T*

在这个实现中,我们使用了 std::move 函数将 m_data[i] 的内容移动到 newData[i] 中,从而进行深复制,避免了多个 std::string 对象共享同一块内存空间的问题。同时,在析构函数中也只需要简单地使用 delete[] 删除 m_data 指向的内存即可。

3.2 move函数

std::move 是一个 C++11 中引入的函数,它能够将一个对象的值转移到另一个对象中,同时将原对象置于一种“移动状态”,从而避免不必要的对象复制和销毁。

std::move 本质上是将一个左值引用转换成右值引用。在 C++ 中,左值引用是指向左值的引用,右值引用是指向右值的引用。左值是可以取地址的、有持久性的、具名的、具有明确定义的生命周期的值,而右值则是无法取地址的、临时的、没有名称的、生命周期不确定的值。在 C++11 中,我们可以通过使用 && 运算符来声明右值引用。

具体来说,当我们调用 std::move 函数时,它将接受一个左值引用,并返回一个右值引用,表示该对象的值可以被移动。通常情况下,我们会将返回的右值引用绑定到另一个对象上,从而将原对象的值移动到新对象中。例如:

std::vector<int> v1{1, 2, 3};
std::vector<int> v2 = std::move(v1); // 将 v1 的值移动到 v2 中

需要注意的是,在使用 std::move 移动对象时,只会移动对象的值,也就是说会将对象的成员变量的值复制到新的内存位置,但不会复制对象的状态,比如对象的引用计数、对象的资源句柄等等。移动完成后,原对象的值会被置为“移后”的状态,这个状态下对象的行为是未定义的,我们不能再对其进行读取或修改。在移动一个对象之后,如果我们需要继续使用该对象,就必须重新对其进行赋值或初始化。

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

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

相关文章

熬夜30天吃透这九大Java核心专题,我收割了3个大厂offer

这次一共收割了3个大厂offer&#xff0c;分别是蚂蚁金服、美团和网易&#xff0c;特意分享这次对我帮助非常大的宝典资料&#xff0c;一共涉及九大核心专题&#xff0c;分别是计算机网络、操作系统、MySQL、Linux、JAVA、JVM、Redis、消息队列与分布式、网站优化相关&#xff0…

DSF深度搜索时到底是如何回溯的(小tip)

这一段让我迷了两次&#xff0c;为什么回溯的时候&#xff0c;恢复了最后一位&#xff0c;往上递归一层之后&#xff0c;把最后一位填在它前一位&#xff0c;但是原本的前一位没有恢复&#xff0c;最后一位要怎么办&#xff1f;其实这还是递归没明白 也就是这一步是如何实现的 …

一点就分享系列(实践篇6——上篇)【迟到补发】Yolo-High_level系列算法开源项目融入V8 旨在研究和兼容使用【持续更新】

一点就分享系列&#xff08;实践篇5-补更篇&#xff09;[迟到补发]—Yolo系列算法开源项目融入V8旨在研究和兼容使用[持续更新] 题外话 去年我一直复读机式强调High-level在工业界已经饱和的情况&#xff0c;目的是呼吁更多人看准自己&#xff0c;不管是数字孪生交叉领域&#…

C++基础了解-21-C++ 继承

C 继承 一、C 继承 面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类&#xff0c;这使得创建和维护一个应用程序变得更容易。这样做&#xff0c;也达到了重用代码功能和提高执行效率的效果。 当创建一个类时&#xff0c;不需要重新编写新的…

量子计算(8)pyqpanda编程3测量操作

作为一名高产博主&#xff0c;小编我一天不写文章就浑身难受&#xff0c;这不&#xff0c;一闲下来就来给大家科普量子计算编程操作了。 今天我们要来探讨“测量操作”&#xff0c;众所周知&#xff0c;薛定谔的猫是一种既死又活的状态&#xff0c;很多人认为&#xff0c;猫是死…

Java代码优化|提高代码质量的一些小技巧

1.需要 Map 的主键和取值时&#xff0c;应该迭代 entrySet()当循环中只需要 Map 的主键时&#xff0c;迭代 keySet() 是正确的。但是&#xff0c;当需要主键和取值时&#xff0c;迭代 entrySet() 才是更高效的做法&#xff0c;比先迭代 keySet() 后再去 get 取值性能更佳。正例…

Git设置SSH Key

一、git 配置 &#xff08;1&#xff09;打开 git 命令窗口 &#xff08;2&#xff09;配置用户名&#xff08;填自己的姓名&#xff09; git config --global user.name “xinyu.xia” &#xff08;3&#xff09;配置用户邮箱&#xff08;填自己的邮箱&#xff0…

Python+Yolov8目标识别特征检测

Yolov8目标识别特征检测如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01;前言这篇博客针对<<Yolov8目标识别特征检测>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学习与应用推荐…

Easy Deep Learning——PyTorch中的自动微分

目录 什么是深度学习&#xff1f;它的实现原理是怎么样的呢&#xff1f; 什么是梯度下降&#xff1f;梯度下降是怎么计算出最优解的&#xff1f; 什么是导数&#xff1f;求导对于深度学习来说有何意义&#xff1f; PyTorch 自动微分&#xff08;自动求导&#xff09; 为什么…

小孩用什么样的台灯比较好?2023眼科医生青睐的儿童台灯推荐

小孩子属于眼睛比较脆弱的人群&#xff0c;所以选购护眼台灯时&#xff0c;选光线温和的比较好&#xff0c;而且调光、显色效果、色温、防蓝光等方面也要出色&#xff0c;否则容易导致孩子近视。 1、调光。台灯首先是照度高&#xff0c;国AA级&#xff0b;大功率发光&#xff0…

C++学习笔记(以供复习查阅)

视频链接 代码讲义 提取密码: 62bb 文章目录1、C基础1.1 C初识&#xff08;1&#xff09; 第一个C程序&#xff08;2&#xff09;注释&#xff08;3&#xff09;变量&#xff08;4&#xff09;常量&#xff08;5&#xff09;关键字&#xff08;6&#xff09;标识符命名规则1.2 …

前端jQuery ajax请求,后端node.js使用cors跨域

前言 跨域&#xff0c;一句话介绍&#xff1a; 你要请求的URL地址与当前的URL地址&#xff0c;协议不同、域名不同、端口不同时&#xff0c;就是跨域。 步入正题 前端&#xff0c;jQuery ajax请求 $.ajax({async: false,method: post,//URl和端口与后台匹配好&#xff0c;当…

HTTPS加密解析

日升时奋斗&#xff0c;日落时自省 目录 1、加密解释 2、对称加密 3、非对称加密 4、证书 HTTPS&#xff08;HyperText Transfer Protocol over Secure Socket Layer&#xff09;也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层 HTTP协议内容都是按…

Docker学习(二十一)构建 java 项目基础镜像

目录1.下载 JDK 包2.编写 Dockerfile3.构建镜像4.创建容器测试1.下载 JDK 包 JDK各版本官网下载地址&#xff1a; https://www.oracle.com/java/technologies/downloads/archive/#JavaSE 这里我们以 JDK 8u351 为例&#xff0c;点击 Java SE (8U211 and later)。 点击下载 jd…

自动化测试实战篇(9),jmeter常用断言方法,一文搞懂9种测试字段与JSON断言

Jmeter常用的断言主要有&#xff0c;JSON断言和响应断言这两种方式。 断言主要就是帮助帮助人工进行快速接口信息验证避免繁杂的重复的人工去验证数据 第一种响应断言Apply to&#xff1a;表示应用范围测试字段&#xff1a;针对响应数据进行不同的匹配响应文本响应代码响应信息…

WPF 自定义DataGrid控件样式模板5个

WPF 自定义DataGrid控件样式样式一&#xff1a;样式代码&#xff1a;<!--DataGrid样式--><Style TargetType"DataGrid"><!--网格线颜色--><Setter Property"CanUserResizeColumns" Value"false"/><Setter Property&q…

day48第九章动态规划(二刷)

今日任务 198.打家劫舍213.打家劫舍II337.打家劫舍III 今天就是打家劫舍的一天&#xff0c;这个系列不算难&#xff0c;大家可以一口气拿下。 198.打家劫舍 题目链接&#xff1a; https://leetcode.cn/problems/house-robber/description/ 题目描述&#xff1a; 你是一个…

[ Azure ] Episode 03 | 描述云计算运营中的 CapEx 与 OpEx,如何区分 CapEx 与 OpEx

正常情况如果你不是会计&#xff0c;或者对钱相关的数字比较敏感的财务&#xff0c;本文的一些东西你不会接触的&#xff0c;但是最为云架构或者云运营&#xff0c;你可能会遇到如何采购亦或者估算的我成本和运营成本等等&#xff0c;所以本文的一些知识点就需要进行一定的了解…

Fikker安装SSL证书

Fikker 基于nginx&#xff0c; 订单详细中下载nginx格式&#xff0c; 解压后包含 yourdomain.com.crt 和 yourdomain.com.key 2个文件&#xff0c;将内容粘贴到输入框中.1、说明&#xff1a;在【主机管理】中设置网站域名对应的SSL 数字证书&#xff08;CRT/CER&#xff09;和证…

MySQL 02 :三层结构、备份删除数据库

MySQL 02 &#xff1a;数据库三层结构-破除MySQL神秘 请添加图片描述 通过golang操作MySQL 创建删除数据库 备份恢复数据库 第一次需要配置环境&#xff0c;否则会报错 报错&#xff1a;mysqldump: Got error: 1045: Access denied for user ‘root’‘localhost’ (using …