C++基础——vector的详解与运用
vector的介绍
文档介绍
- vector是表示可变大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
- 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
vector的使用
下面是对于vector的基本用法的讲解
vector的初始化
| (constructor)构造函数声明 | 接口说明 |
|---|---|
| vector()(重点) | 无参构造 |
| vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
| vector (const vector& x); (重点) | 拷贝构造 |
| vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
主要使用的是无参构造和拷贝构造,其他两种不常用,下面代码一并介绍
void test1()
{//1.vector()(重点) | 无参构造 vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto c : v1){cout << c << " ";}cout << endl;//2.vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个valvector<int> v2(10, 0);for (auto c : v2){cout << c << " ";}cout << endl;//3.vector(const vector & x); (重点) | 拷贝构造 vector<int> v3(v1);for (auto c : v3){cout << c << " ";}cout << endl;//4.vector(InputIterator first, InputIterator last);vector<int> v4(v2.begin(), v2.end());for (auto c : v4){cout << c << " ";}cout << endl;
}
int main()
{test1();return 0;
}
vector iterator迭代器的使用
vector的迭代器有两种,正向和反向迭代器
正向:begin() 和 end() 分别表示获取第一个数据的位置,获取最后一个数据的下一个位置
反向:rbegin() 和 rend() 分别表示获取最后一个数据的位置,获取第一个元素的前一个位置
//迭代器的名称分别为 iterator 和reverse_iterator
void test2()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;//auto rit=v.rbegin();vector<int>::reverse_iterator rit = v.rbegin();while (rit != v.rend()){cout << *rit << " ";++rit;}cout << endl;for (auto c : v){cout << c << " ";}cout << endl;
}
int main()
{test2();return 0;
}
vector 空间增长问题
使用几种函数来管理和查看vector的空间
| 容量空间 | 接口说明 |
|---|---|
| size | 获取数据个数 |
| capacity | 获取容量大小 |
| empty | 判断是否为空 |
| resize | 改变vector的size |
| reserve | 改变vector的capacity |
主要是对于resize和reserve的使用,empty就是判别是否为空,size和capacity是查看当前vector的数据个数和容量
void test3()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << v.size() << endl;cout << v.capacity() << endl;v.resize(20);cout << "更改size为20" << endl;cout << v.size() << endl;cout << v.capacity() << endl;v.reserve(40);cout << "更改capacity为40" << endl;cout << v.size() << endl;cout << v.capacity() << endl;v.resize(50);cout << "更改size为50" << endl;cout << v.size() << endl;cout << v.capacity() << endl;
}
void test4()
{vector<int> v;size_t ans = v.capacity();for (int i = 0; i < 200; i++){v.push_back(i);if (ans != v.capacity()){cout << "capacity为:" << ans << endl ;}ans = v.capacity();}
}int main()
{test4();return 0;
}
注意:capacity的增长倍数是由环境决定的,vs下在1.5倍数左右,在Linux下近乎2倍,且关于resize和reserve,我们只需要知道resize底层是调用reserve的,满足size<=capacity
vector的增删改查
| vector的增删改查 | 接口说明 |
|---|---|
| push_back | 尾插 |
| pop_back | 尾删 |
| find | 查找指定的数据,返回下标(在算法模块,不在vector的成员函数中) |
| insert | 在pos位置上插入指定数值val |
| erase | 删除指定pos位置上的数据 |
| swap | 交换两个vector对象的数据空间 |
| operator[ ] | 和数组一样,通过下标方括号来访问vector的数据 |
STL分为两大模块,容器和算法,容器例如vector、list、set等,算法是在头文件<algorithm>中,是将容器中通用的函数归档在<algorithm>中,按照迭代器的不同,不同容器能使用相同或不同的函数。
push_back 和 pop_back 以及operator[ ]的使用
简单理解,不必做详解
void test5()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//push_back的使用,传参val const value_type& val value_type实际就是T 模板类for (auto c : v){cout << c << " ";}cout << endl;//pop_back 的使用,不必传参,尾删v.pop_back(); for (auto c : v){cout << c << " ";}cout << endl;//operator[]的使用 可以访问指定下标位置元素,也可赋值cout << v[2] << endl;v[2] = 10;cout << v[2] << endl;
}
int main()
{test5();return 0;
}
find
在<algorithm>算法中,InputIterator find (InputIterator first, InputIterator last, const T& val);
#include<algorithm>
void test6()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);auto it =find(v.begin(), v.end(), 5);if (it != v.end()){cout << *it << endl;}else if(it == v.end()){cout << "没有找到val,返回的是v.end()" << endl;}
}
int main()
{test6();return 0;
}
insert
insert的插入,实际上就是先得到要插入的数值个数n,然后将vector中的元素后移n位,然后依次填入插入的数据
void test7()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto c : v){cout << c << " ";}cout << endl;//1.iterator insert (iterator position, const value_type& val); auto it=v.insert(v.begin() + 1, 10);cout << "返回迭代器指向的数值为:" << *it << endl;for (auto c : v){cout << c << " ";}cout << endl;//2.void insert (iterator position, size_type n, const value_type& val);v.insert(v.begin(), 5, 6);for (auto c : v){cout << c << " ";}cout << endl;//3.void insert(iterator position, InputIterator first, InputIterator last);v.insert(v.begin(), v.begin(), v.end());for (auto c : v){cout << c << " ";}cout << endl;vector<int> v1; //后两个参数,只要是迭代器即可,可以是其他容器的迭代器数据插入过来v1.insert(v1.begin(), v.begin(), v.begin()+6);for (auto c : v1){cout << c << " ";}cout << endl;}
int main()
{test7();return 0;
}
erase
void test8()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//指定pos迭代器删除auto it =v.erase(v.begin()); //返回的是删除之后的v.begin()cout << *it << endl;for (auto c : v){cout << c << " ";}cout << endl; //区间删除it=v.erase(v.begin() + 1, v.begin()+2);cout << *it << endl; //返回的是删除之后的v.begin() + 1 但是要注意越界问题for (auto c : v){cout << c << " ";}cout << endl;
}
int main()
{test8();return 0;
}
迭代器失效
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,所以迭代器失效实际上就是迭代器底层对应指针所指向的空间被销毁了,而使用一窥啊已经倍释放的空间,当继续使用已经失效的迭代器,会造成程序崩溃。
迭代器失效的情景
- 引起底层空间的改变都有可能是造成迭代器失效,如resize、reserve、insert、push_back等。他们的同意特性就是会在底层调用reserve来判断增加元素时,是否需要扩容。
- 指定位置元素的删除操作,erase(pos)
第一种场景,统一导致迭代器失效的是,由于增加元素或者手动扩容,导致vector扩容,然而扩容机制,底层是新建一个T指针数组,拷贝原有数据到新数组中,然后释放原来的数组空间,所以迭代器会失效(此时的迭代器指向的是一块被释放的空间),导致程序崩溃
void test9()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//底层的改变实际上就是是否扩容了//先获得当前vector的首元素的迭代器vector<int>::iterator it = v.begin(); cout << *it << endl;//1.如果经历了resize ,触发扩容,迭代器失效 代码为 -1073741819。//v.resize(50, 0);//cout << *it << endl;//2.如果使用reserve手动扩容的话,也会导致迭代器失效//v.reserve(100);//cout << *it << endl; //代码为 -1073741819。//3.如果是插入元素,导致扩容,也会导致迭代器失效//v.insert(v.begin(), 100, 1);//cout << *it << endl; //代码为 -1073741819。//4.如果是push_back 导致扩容,也会发生迭代器失效for (int i = 0; i < 100; i++){v.push_back(i);}cout << *it << endl; //代码为 -1073741819。
}
int main()
{test9();return 0;
}
第二种场景,erase函数,删除指定pos位置数据,或者删除迭代器区间的数据。我们先用auto it=v.begin()+5,获得迭代器,然后我们删除一个元素,删除之后有两种可能:1.it指向的位置还有元素,那么按照道理来说 cout一下还是能正常输出数值 2.it位置已经没有元素了,此时it指向的空间已经是vector.end()之后了,再次访问it的时候,就会报错,相当于越界了。
综合上述两种可能,因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。
void test10()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);auto it = v.begin();v.erase(v.begin());//v.erase(v.begin(), v.end());cout << *it << endl; //代码为 -1073741819
}
int main()
{test10();return 0;
}
迭代器失效解决办法:在使用前,对迭代器重新赋值即可。
总结
vector的优点:
1.支持随机访问(以[]的方式)
2.cpu高速缓存效率高
vector的缺点:
1.中间和头部删除元素效率低(要移动数组元素)
2.扩容问题(扩容会导致迭代器失效)
总结:
- vector的使用实际上大部分的函数与string中的用法相似,我们只需要知道一点的是,vector是一个可变长的连续有序序列即可,不管是空间还是物理上,数据是连续的。
- vector的初始的capacity的大小和初始化的数据个数有关,然后依次为基点,在VS平台以近似1.5倍扩容,在Linux下以2倍扩容。
- 在vector中我们初步认识到了**的存在,这是算法,包含在STL中,STL分为容器和算法,**中是总结所有容器所需要的接口,通过迭代器来实现。不同的容器有不同的迭代器,能实现不同的算法接口。
- 迭代器失效问题,是string以及vector在涉及到扩容或者erase中会发生的问题,但是erase返回参数为iterator,所以只需要再次赋值即可,而扩容导致的迭代器失效,我们只能再次对迭代器重新赋值,取得新的迭代器。随用随取,在使用前,对迭代器重新赋值。
- 综上,我们可以通过vector,不必顾忌数组的长度问题,通过相应的便捷的接口,来实现更多可能性的操作。
相关文章:
C++基础——vector的详解与运用
vector的介绍 文档介绍 vector是表示可变大小数组的序列容器。就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,…...
const指针,星号判断方法
一 示例代码 1. const char *p // 指向常量的指针 2. char const *p // 指向常量的指针 3. char * const p // 指针常量二 判断方法 const在星号左边,指向常量的指针,指针p可修改。 const在星号右边,指针常量,指针p不可修改。...
移动摄像头专网需要解vlan,如何解决
🏆本文收录于「Bug调优」专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&…...
5.27周报
这两周邻近毕业故没有很多时间来学习课余内容,另外最近身体有些不舒服【偏头痛】,所以学的内容不多,包括SVM向量机和ResNet【不包括代码复现】 1.SVM支持向量机的大概内容 1、目的: 主要内容是如何找到分类的那条线【超平面】—…...
C-数据结构-树状存储的基本实现
/* 理解和记忆递归的关键在于把握递归的本质和函数调用的过程。递归函数在每次调用时会把当前状态压入调用栈,直到满足终止条件后开始回溯。理解基准条件和递归步骤:每个递归函数都需要有基准条件(如节点为空时返回),并…...
指纹识别经典图书、开源算法库、开源数据库
目录 1. 指纹识别书籍 1.1《精通Visual C指纹模式识别系统算法及实现》 1.2《Handbook of Fingerprint Recognition》 2. 指纹识别开源算法库 2.1 Hands on Fingerprint Recognition with OpenCV and Python 2.2 NIST Biometric Image Software (NBIS) 3. 指纹识别开源数…...
嵌入式之译码器
系列文章目录 译码器嵌入式之译码器 嵌入式之译码器 系列文章目录一、译码器定义二、常见类型的译码器三、工作原理 一、译码器定义 译码器(Decoder)是一种数字电路,其主要功能是从输入的编码信号中解码出特定的信息或控制信号。 译码器通常…...
分成sum接近的2个集合,返回相对小的sum
题目描述:给定一个正数数组arr,请把arr中所有的数分成两个集合,尽量让两个集合的累加和接近,返回最接近的情况下,较小集合的累加和sum。 way:选还是不选 //arr[index...]可以自由选择,返回累加和尽量接近…...
SpringBoot前置知识01-SPI接口
SpringBoot前置知识-SPI接口 介绍 Java中SPI是一种服务发现机制,或者说是一种思想,亦是一种约定。其实JDK中的JDBC就是使用了这种用思想,JDBC在JDK中只定义了接口,并没有实现类,连接什么数据库就要引入什么数据库的驱…...
数学建模--LaTeX的基本使用
目录 1.回顾 2.设置这个页眉和页脚 3.对于字体的相关设置 4.对于这个分级标题的设置 5.列表的使用 6.插入图片 1.回顾 (1)昨天我们了解到了这个latex的使用基本常识,以及这个宏包的概念,区域的划分,不同的代码代…...
授权调用: 介绍 Transformers 智能体 2.0
简要概述 我们推出了 Transformers 智能体 2.0! ⇒ 🎁 在现有智能体类型的基础上,我们新增了两种能够 根据历史观察解决复杂任务的智能体。 ⇒ 💡 我们致力于让代码 清晰、模块化,并确保最终提示和工具等通用属性透明化…...
流媒体内网穿透/组网/视频协议转换EasyNTS上云网关如何更改密码?
EasyNTS上云网关的主要作用是解决异地视频共享/组网/上云的需求,网页对域名进行添加映射时,添加成功后会生成一个外网访问地址,在浏览器中输入外网访问地址,即可查看内网应用。无需开放端口,EasyNTS上云网关平台会向Ea…...
HTML5的标签(文本链接、图片路径详解)
目录 前言 一、文本链接 超链接表述 二、图片路径详解 绝对路径 相对路径 网络路径 前言 一、文本链接 超链接表述 HTML 使用标签<a>来设置超文本链接 超链接可以是一个字,一个词,或者一组词,也可以是一幅图像,…...
React Native 之 Linking(链接)(十五)
URL Scheme是什么 URL Scheme是一种机制,主要用于在移动应用程序中打开另一个应用程序或执行特定操作。 定义与原理: URL Scheme允许应用程序通过特定的URL格式与其他应用程序进行交互。 它通过在应用程序中注册一个自定义的URL Scheme,并在…...
Java实现图书系统
首先实现一个图书管理系统,我们要知道有哪些元素? 1.用户分成为管理员和普通用户 2.书:书架 书 3.操作的是: 书架 目录 第一步:建包 第二步:搭建框架 首先:完成book中的方法 其次:完成BookList 然后:完成管理员界面和普通用户界面 最后:Main 第三步:细分方法 1.退…...
Git提交和配置命令
一、提交代码到仓库 在软件开发中,版本控制是一个至关重要的环节。而Git作为目前最流行的版本控制系统之一,为我们提供了便捷高效的代码管理和协作工具。在日常开发中,我们经常需要将本地代码提交到远程仓库,以便于团队协作和版本…...
已解决java.lang.ExceptionInInitializerError: 初始化程序中的异常错误的正确解决方法,亲测有效!!!
已解决java.lang.ExceptionInInitializerError: 初始化程序中的异常错误的正确解决方法,亲测有效!!! 目录 问题分析 报错原因 解决思路 解决方法 分析错误栈信息 检查静态初始化块和静态变量 验证资源和配置 使用日志记录…...
报表显示中,是否具备条件格式功能设计?
**报表显示中确实具备条件格式功能设计**。条件格式是一种根据特定条件对单元格或单元格区域进行格式设置的功能,它可以帮助用户更直观地理解和分析数据。 通过条件格式,用户可以设置多种条件,如单元格值的大小、是否包含特定文本等…...
完全二叉树查找
描述 有一棵树,输出某一深度的所有节点,有则输出这些节点,无则输出EMPTY。该树是完全二叉树。 输入描述 输入有多组数据,遇到0时终止输入。 每组输入一个n(1<n<1000),然后将树中的这n个节点依次输入ÿ…...
Web安全:SQL注入之时间盲注原理+步骤+实战操作
「作者简介」:2022年北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖系统安全、信息收集等…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
