深入理解与应用C++ Vector

news2024/11/27 12:57:15

1. C++ Vector 简介与基本使用

C++ 的 vector 是一个序列容器,用于表示可变大小的数组。它结合了数组的高效元素访问和动态大小调整的灵活性。与静态数组相比,vector 的大小可以根据需要自动调整,这是通过在底层使用动态数组来实现的。当新元素被插入到 vector 中时,如果现有空间不足以容纳更多元素,将会自动重新分配一个更大的数组空间,并将所有现有元素移动到新的存储位置。这个动态扩容机制虽然提供了极大的灵活性,但也可能是性能的瓶颈,特别是在元素频繁插入的情况下。

具体模拟实现请移步查看https://github.com/hqxnb666/C-/blob/main/vector.h

1.1 Vector 的构造与初始化

vector 提供了多种构造函数:

  • 默认构造函数:创建一个空的 vector
  • 填充构造函数:创建一个具有初始大小的 vector,每个元素都是拷贝指定值。
  • 范围构造函数:通过迭代器指定的范围来构造 vector
  • 拷贝构造函数:通过复制另一个 vector 的所有元素来构造新的 vector

1.2 Vector 的操作

vector 支持多种操作,包括访问(使用 operator[])、插入(push_backinsert)、删除(pop_backerase)和容量调整(resizereserve)。特别需要注意的是,很多操作可能会导致 vector 进行重新分配空间,这将导致已有的迭代器、指针和引用失效。

1.2.1 Vector Iterator 的使用

vector 的迭代器是一种允许遍历容器元素的工具,表现得类似于指针。迭代器对于在 vector 中进行元素访问和修改都非常有用。了解如何正确地使用迭代器对于编写高效和安全的 C++ 代码至关重要。

迭代器类型
  • 正向迭代器 (iterator, const_iterator): 允许读写操作或只读操作,并能向前移动(即递增操作)来访问 vector 的元素。
  • 反向迭代器 (reverse_iterator, const_reverse_iterator): 允许从容器末尾开始向开始方向遍历元素。
迭代器的使用

迭代器最常见的用法是在循环中遍历 vector。例如,使用正向迭代器遍历所有元素:

 

std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;

 

基本操作
  • begin()end(): begin() 返回指向 vector 第一个元素的迭代器,而 end() 返回指向 vector 末尾(最后一个元素的下一个位置)的迭代器。
  • rbegin()rend(): rbegin() 返回指向 vector 最后一个元素的反向迭代器,rend() 返回指向 vector 开始前一个位置的反向迭代器。

1.2.2 vector 空间增长问题  

capacity 的代码在 vs g++ 下分别运行会发现, vs capacity 是按 1.5 倍增长的, g++ 是按 2 倍增长的 。这个问题经常会考察,不要固化的认为,vector 增容都是 2 倍,具体增长多少是根据具体的需求定义的。vs PJ 版本 STL g++ SGI 版本 STL reserve只负责开辟空间,如果确定知道需要用多少空间, reserve 可以缓解 vector 增容的代价缺陷问题。 resize在开空间的同时还会进行初始化,影响size

 

// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
 vector<int> v;
 size_t sz = v.capacity();
 v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
 cout << "making bar grow:\n";
 for (int i = 0; i < 100; ++i) 
 {
 v.push_back(i);
 if (sz != v.capacity())
 {
 sz = v.capacity();
 cout << "capacity changed: " << sz << '\n';
 }
 }
}

 1.2.3 vector 增删查改

 https://github.com/hqxnb666/C-/blob/main/vector.h

1.2.4 vector 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了 封装 ,比如: vector 的迭代器就是原生态指针 T* 。因此 迭代器失效,实际就是迭代器底层对应指针所指向的 空间被销毁了,而使用一块已经被释放的空间 ,造成的后果是程序崩溃 ( 如果继续使用已经失效的迭代器, 程序可能会崩溃 )
对于 vector 可能会导致其迭代器失效的操作有:
1. 会引起其底层空间改变的操作,都有可能是迭代器失效 ,比如: resize reserve insert assign 、push_back等。
#include <iostream>
using namespace std;
#include <vector>
int main()
{
 vector<int> v{1,2,3,4,5,6};
 
 auto it = v.begin();
 
 // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
 // v.resize(100, 8);
 
 // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
 // v.reserve(100);
 
 // 插入元素期间,可能会引起扩容,而导致原空间被释放
 // v.insert(v.begin(), 0);
 // v.push_back(8);
 
 // 给vector重新赋值,可能会引起底层容量改变
 v.assign(100, 8);
 
 /*
 出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
空间,而引起代码运行时崩溃。
 解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
赋值即可。
 */
 while(it != v.end())
 {
 cout<< *it << " " ;
 ++it;
 }
 cout<<endl;
 return 0;
}

而有的朋友就要问了:既然是由于it没有被及时更新,那我们为什么不直接传引用呢?

在 C++ 中,迭代器通常以值传递的形式在函数中传递。这是因为迭代器本身通常设计得很轻量(很多情况下,迭代器的实现仅是一个或一组指针),所以按值传递的开销很小。更重要的是,按值传递迭代器可以避免外部修改对内部逻辑的影响,从而保持函数的封装性和独立性。如果迭代器作为引用传递,那么在 insert 或其他修改容器的操作中,如果发生了容器的重新分配,迭代器可能就会指向无效的内存区域。函数外部的代码可能在不知情的情况下继续使用这个已经失效的迭代器,从而引发错误或崩溃。此外,引用传递迭代器可能会让函数的调用者误以为他们传入的迭代器在函数执行后仍然有效,从而忽略了必要的更新迭代器的操作。insert 函数返回新元素插入位置的迭代器,这提供了一种更新和使用新的有效迭代器的安全方式。调用者应该使用这个新返回的迭代器,而不是继续使用之前的迭代器。这种设计促使程序员在每次修改操作后都显式地更新他们的迭代器引用,从而增加代码的安全性和可维护性。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 4, 5};
    auto it = vec.begin() + 2; // 指向元素 4
    it = vec.insert(it, 3); // 在 4 之前插入 3

    // 输出新的 vector 内容
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 继续使用 it,现在 it 指向新插入的 3
    ++it; // 安全地移动到下一个元素,即原来的 4
    std::cout << "Next element after 3: " << *it << std::endl;
}

对于erase,如果我们不进行扩容,只删除会不会还会引起迭代器失效呢?

答案是:会的

#include <iostream>
using namespace std;
#include <vector>
int main()
{
 int a[] = { 1, 2, 3, 4 };
 vector<int> v(a, a + sizeof(a) / sizeof(int));
 // 使用find查找3所在位置的iterator
 vector<int>::iterator pos = find(v.begin(), v.end(), 3);
 // 删除pos位置的数据,导致pos迭代器失效。
 v.erase(pos);
 cout << *pos << endl; // 此处会导致非法访问
 return 0;
}
erase 删除 pos 位置元素后, pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代
器不应该会失效,但是:如果 pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是
没有元素的,那么 pos 就失效了。因此删除 vector 中任意位置上元素时, vs 就认为该位置迭代器失效
了。

2. Vector 深度剖析及模拟实现

vector 的实现细节可以深入理解其性能和存储特性。std::vector的核心框架接口的模拟实现bit::vector  

2.1 模拟实现 Vector

深入探讨 vector 的模拟实现可以帮助更好地理解其内部机制。例如,使用 memcpy 进行元素拷贝在面对非平凡数据类型时可能会出现问题,因为 memcpy 只进行浅拷贝。在自定义数据类型涉及深层资源管理时,浅拷贝可能导致资源泄漏或程序崩溃。

使用 memcpy 拷贝问题
假设模拟实现的 vector 中的 reserve 接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?

 

问题分析:
1. memcpy 是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
2. 如果拷贝的是自定义类型的元素, memcpy 既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy 的拷贝实际是浅拷贝

 

结论

通过本文的学习,我们不仅理解了 vector 的基本用法和实现原理,还探讨了其在实际编程中的高级应用。理解这些概念将有助于开发更高效、更健壯的软件系统。对于希望深入学习 C++ 或进行系统性能优化的开发者来说,深入掌握 vector 的使用和原理是非常有价值的。

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

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

相关文章

48.乐理基础-音符的组合方式-休止符

休止符 音乐中总有一些停顿的地方&#xff0c;一次停顿多久是创作人固定好的&#xff0c;休止符就是用来表示每一次停顿多久 需要停顿的位置就用 0 来表示&#xff0c;数字 0 就是简谱中的休止符 音符有全音符、二分音符、四分音符、八分音符、十六分音符、三十二分音符等&…

微信小程序Vue+uniapp餐饮美食订餐骑手配送系统9g60o

本小程序uniapp菜品帮采用Java语言和Mysql数据库进行设计&#xff0c;技术采用微信小程序&#xff0c;可以不安装App软件就实现订餐。本系统实现管理员和用户、商家、配送员四个角色的功能。用户主要在微信端操作&#xff0c;内容有菜品信息&#xff0c;用户可以在线点餐和管理…

还在用Postman?试试BB-API,或许更适合你!

什么是BB-API 功能全面 BB-API是一个功能强大的HTTP模拟请求工具&#xff0c;支持模拟GET、POST、PUT、DELETE、HEAD、OPTIONS、PATCH等多种HTTP请求。这使得开发人员能够方便地进行接口测试&#xff0c;验证后端服务的正确性。 离线模式 与市面上许多在线API管理工具不同&…

鸿蒙系统编译方式

鸿蒙系统编译 编译原理编译方式概述hb编译ohos-buildhb安装编译使用build脚本hpmhpm介绍编译举例说明综合应用举例虚拟机中编译docker中使用hpm编译编译原理 编译构建指导:https://docs.openharmony.cn/pages/v4.0/zh-cn/device-dev/subsystems/subsys-build-all.md,文档介绍…

js前端获取农历日期

对于公历来说&#xff0c;直接 new 一个 Date 就能获取到&#xff0c;而对于农历来讲可就很难了&#xff0c;因为农历需要有许多复杂计算&#xff0c;虽然一般用的甚少&#xff0c;但对于某些场景来说还是会需要的&#xff0c;那么怎样获取农历日期呢&#xff1f; 这里推荐一个…

2024初会押题来了!经济法和实务,内部资料,赶快做一做!

初级会计考试考前冲刺策略 1. 核心知识点掌握 考前冲刺首先需要对核心知识点进行梳理和重点复习。《初级会计实务》和《经济法基础》是考试的两个主要科目&#xff0c;考生需要确保对其中的高频考点有深刻的理解。 2. 高效记忆技巧 对于容易遗忘的知识点&#xff0c;可以采用…

C++进阶:AVL树详解及模拟实现(图示讲解旋转过程)

C进阶&#xff1a;AVL树详解及模拟实现&#xff08;图示讲解旋转过程&#xff09; 之前在搜索二叉树最后早就埋下伏笔&#xff0c;来介绍AVL树和红黑树&#xff0c;今天就先来第一个吧 文章目录 1.AVL树介绍1.1概念介绍1.2核心性质 2.项目文件规划3.整体框架&#xff08;节点和…

管仲发动的粮食战争令人惊醒

各种类型的战争&#xff0c;在中国春秋战国时代就已经包罗万象、炉火纯青了&#xff0c;第一仲父管仲无疑是其中最伟大的军事家之一。 时至今日&#xff0c;他留给人们的最大印象&#xff0c;应该是孔子那句话“微管仲&#xff0c;吾其被发左衽矣。” 也就是说&#xff0c;如果…

2024生日快乐祝福HTNL源码修复版

源码介绍 2024生日快乐祝福HTNL源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c; 源码截图 源码下载 2024生日快乐祝福HTNL源码

证明力引导算法forceatlas2为什么不是启发式算法

一、基本概念 吸引力 F a ( n i ) ∑ n j ∈ N c t d ( n i ) ω i , j d E ( n i , n j ) V i , j \displaystyle \bm{F}_a(n_i) \sum_{n_j \in \mathcal{N}_{ctd}(n_i)} \omega_{i,j} \; d_E(n_i,n_j) \bm{V}_{i,j} Fa​(ni​)nj​∈Nctd​(ni​)∑​ωi,j​dE​(ni​,nj​…

大型医疗挂号微服务“马上好医”医疗项目(4)设计一个医院方接口

如何构建一个医院方接口 一、如何进行数据库建模 数据库建模一般需要使用工具PowerDesign&#xff0c;但是其实在navicat中是有类似的功能的 二、分析医院接口会有什么字段 其实很多的同学在入行的时候会有一个问题&#xff0c;没有设计思维。 表字段的设计方案 状态字段…

[蓝桥杯]真题讲解:数三角(枚举+STL)

[蓝桥杯]真题讲解&#xff1a;数三角&#xff08;枚举STL&#xff09; 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解&#xff1a;数三角&#xff08;枚举STL&#xff09; 二、正解代码 1、C #include<bits/stdc.h> #define int long…

LeetCode_栈和队列相关OJ题目

✨✨所属专栏&#xff1a;LeetCode刷题专栏✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 上一篇&#xff1a;数据结构_栈和队列(Stack & Queue)-CSDN博客 有效的括号 解析: 这里我们用数组实现的栈来解决这个问题&#xff0c;在有了栈的几个基础接口之后&#xff0c;我们运用这…

【Esp32S3 | Arduino】在Arduino中使用C++的高级特性

文章目录 前言一、Arduino中的Vector示例代码二、Arduino中的Map示例代码前言 最近在玩Arduino,自上次发现Arduino可以用Template,能使用高级宏后,这次发现Arduino竟可以使用C++中的一些STL容器,这属实令人震惊。起因是我打算做一个动态的数组,但是手动实现一些操作属实麻烦…

如何使用Docker安装并运行Nexus容器结合内网穿透实现远程管理本地仓库

前言 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊如何使用Docker安装并运行Nexus容器结合内网穿透实现远程管理本地仓库&#xff0c;希望大家能觉得实用&#xff01; 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496…

‘vue-cli-service‘ is not recognized as an internal or external command解决方案

vue-cli-service is not recognized as an internal or external command, operable program or batch file.解决方案 先进行 &#xff1a; npm install -g vue/cli 命令安装vue cli 是必须的。 如果 npm run build 还是报错 遇到同样的提示&#xff1a; 这时候先安装依赖 np…

树莓派|采集视频并实时显示画面

1、使用SSH远程连接到树莓派 2、新建存放代码的目录 mkdir /home/pi/my_code_directory 3、进入存放代码的目录 cd /home/pi/my_code_directory 4、新建py文件 nano cv2test.py 5、输入代码 import cv2# 打开摄像头 cap cv2.VideoCapture(0)while True:# 读取视频帧ret…

产品推荐 | 基于Intel (Altera) Cyclone V打造的水星Mercury SA1核心板

01 产品概述 水星Mercury SA1片上系统&#xff08;SoC&#xff09;核心板通过结合基于ARM处理器的SoC FPGA、快速DDR3L SDRAM、eMMC flash、QSPI flash、Gigabit Ethernet PHY和RTC形成了一个高性能嵌入式处理方案&#xff0c;结合了CPU系统的灵活性和FPGA原始的、实时的并行处…

软件测试总体报告(实际项目原件Word参考)

软件全套精华资料包清单部分文件列表&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0c;用户需求说明书&#xff0c;概要设计说明书&#xff0c…

信创应用软件之邮箱

信创应用软件之邮箱 文章目录 信创应用软件之邮箱采用信创邮箱的必要性信创邮箱采购需求国产邮箱业务形态国产邮箱代表性品牌CoremailRichmail安宁eyouUMail拓波 邮件安全的发展阶段 采用信创邮箱的必要性 邮箱是天然的数据存储空间&#xff0c;党政和央国企客户在使用过程中存…