深入理解与应用C++ Vector

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; // 指向元素 4it = vec.insert(it, 3); // 在 4 之前插入 3// 输出新的 vector 内容for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;// 继续使用 it,现在 it 指向新插入的 3++it; // 安全地移动到下一个元素,即原来的 4std::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所在位置的iteratorvector<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.mzph.cn/news/837012.shtml

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

相关文章

[原创](Modern C++)现代C++的Lambda表达式常规概念以及细节探讨

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

【Day2:JAVA变量、数据类型、运算符的认识】

目录 1、变量的介绍、定义、使用2、标识符2.1 概念2.2 标识符的命名规则2.3 标识符的命名规范 3、数据类型3.1 基本数据类型3.2 引用数据类型 4、Scanner键盘录入4.1 键盘录入效果介绍4.2 键盘录入的三个步骤 5、运算符5.1 运算符和表达式5.2 算数运算符5.3 自增自减运算符5.4 …

JUC------共享模型------管程

概念 什么是管程 管程&#xff08;Monitor&#xff0c;直译是”监视器“的意思&#xff09;是一种操作系统中的同步机制&#xff0c;它的引入是为了解决多线程或多进程环境下的并发控制问题。 翻译为 Java 领域的语言&#xff0c;就是管理类的成员变量和成员方法&#xff0c;…

在vue3中测试执行typescript代码片段

官方推荐的vitest方法 在vue3typescript项目中&#xff0c;一般来说用vite做构建系统。做测试的话&#xff0c;目前官方推荐的是vitest。官方文档在 https://vitest.dev/guide/ 在vue3typescriptvite5项目中&#xff0c;运行安装命令 npm install -D vitest 就可以把vites…

批量获取oracle库存储过程

场景:需要获取oracle库下的存储过程做备份 示例:获取单个存储过程,sql实现如下 SELECT owner,object_name,object_type FROM dba_objects; SELECT DBMS_LOB.SUBSTR(DBMS_METADATA.GET_DDL(FUNCTION,CONCATSTR,TEST)) FROM DUAL; CREATE OR REPLACE EDITIONABLE FUNCTION &quo…

【教学类-55-02】20240512图层顺序挑战(四格长条纸加黑色边框、4*4、7张 、43200张去掉非7色有23040张,去掉重复样式有几种?)

作品展示 背景需求&#xff1a; 之前的代码吗存在几个问题&#xff0c;最大的问题是不能生成“”长条黑边框”” 【教学类-55-01】20240511图层顺序挑战&#xff08;四格长条纸&#xff09;&#xff08;4*4&#xff09;和“手工纸自制参考图”-CSDN博客文章浏览阅读485次&…

使用Navicat将MySql数据库导入和导出

一&#xff0c;导出数据表 1.使用Navicat打开数据库&#xff0c;右键数据库&#xff0c;点击转储SQL文件&#xff0c;点击结构和数据。 2.选择生成文件的地方 3.等待生成完成 4.生成完成 二&#xff0c;导入数据库表和数据SQL文件 1.新建一个数据库 2.右键选择运行SQl文件 记…

OSPF协议1

OSPF开放式最短路径优先协议 1&#xff0c;OSPF协议因为其传递的是拓扑信息&#xff0c;之后&#xff0c;通过SPF算法将图形结构转换成为树形结构&#xff0c;所以&#xff0c;其计算出的路径不存在环路。并且&#xff0c;OSPF是使用带宽作为开销值的评判标准&#xff0c;所以…

如何利用甘特图来提高资源的是使用效率?

在项目管理中&#xff0c;甘特图是一种常用的工具&#xff0c;用于规划和跟踪项目进度。它通过条形图的形式展示项目的时间表和任务依赖关系&#xff0c;帮助项目经理和团队成员清晰地了解项目的时间线和进度。通过合理利用甘特图&#xff0c;可以显著提高资源的使用效率&#…

一文入门DNS

概述 DNS是一个缩写&#xff0c;可以代表Domain Name System&#xff0c;域名系统&#xff0c;是互联网的一项基础服务。也可以代表Domain Name Server&#xff0c;域名服务器&#xff0c;是进行域名和与之相对应的IP地址相互转换的服务器。DNS协议则是用来将域名转换为IP地址…

1W、2W 3KVAC隔离 宽电压输入 交直两用AC/DC 电源模块 ——TP01(02)AZ 系列

TP01(02)AZ为客户提供一款超小体积模块式开关电源&#xff0c;该系列模块电源输出功率为1W、2W&#xff0c;具有极低的空载损耗&#xff0c;低漏电流仅0.1mA&#xff0c;小体积&#xff0c;隔离耐压高达3KV等特点。产品安全可靠&#xff0c;EMC 性能好&#xff0c;EMC 及安全规…

Ubuntu安装Mysql数据库无法远程连接

1.远程端口未开 2.Mysql 数据库中user表 2.1 用户名root 的host字段味更改为% 允许远程访问&#xff1b; use mysql;SELECT user,host,plugin,authentication_string FROM user;userhostpluginauthentication_stringroot%auth_socketdebian-sys-maintlocalhostcaching_sha2_p…

树莓派配置双网卡分别为AD HOC和AP模式

树莓派配置双网卡分别为AD HOC和AP模式 需求说明&#xff1a;为了实现分级网络管理&#xff0c;将多个无人机分簇&#xff0c;簇间使用AD HOC进行无中心自组织的网络&#xff0c;簇内使用AP-AC模式进行中心化网络。因此&#xff0c;需要配置一台设备&#xff0c;同时完成AD HOC…

Quartz.Net(1)

Quartz 1 Quartz是一个强大的、开源的、轻量级的任务调度框架 Quartz官方文档 2 Quartz中有五个重要的概念 Scheduler 调度器Trigger 触发器Job 工作任务ThreadPool 线程池 &#xff0c;不是CLI的线程池&#xff0c;而是Quartz特有的线程池JobStrore 调度存储&#xff0c;存…

三、配置带HybridCLR的ARCore开发环境

预告 本专栏将介绍如何使用这个支持热更的AR开发插件&#xff0c;快速地开发AR应用。 专栏&#xff1a; Unity开发AR系列 插件简介 通过热更技术实现动态地加载AR场景&#xff0c;简化了AR开发流程&#xff0c;让用户可更多地关注Unity场景内容的制作。 “EnvInstaller…”支…

【eclipse】如何在IDE里创建一个Java Web项目?

如何在eclipse中创建一个动态Web项目并成功运行&#xff1f; 一、 最终效果 懒得写那么多了…我也不知道该怎么写了&#xff0c;有点乱&#xff0c;有问题可以在评论里留言&#xff0c;我看到会解决的&#xff0c;在这个过程中也踩到了一些坑&#xff0c;但好在有CSDN帮助解决…

【C++杂货铺】红黑树

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 红黑树的概念 &#x1f4c1; 红黑树的性质 &#x1f4c1; 红黑树节点的定义 &#x1f4c1; 红黑树的插入操作 &#x1f4c1; 红黑树和AVL树的比较 &#x1f4c1; 全代码展示 &#x1f4c1; 总结 &#x1f308;前言…

文章模版--测试

学习目标&#xff1a; 提示&#xff1a;这里可以添加学习目标 例如&#xff1a; 一周掌握 Java 入门知识 学习内容&#xff1a; 提示&#xff1a;这里可以添加要学的内容 例如&#xff1a; 搭建 Java 开发环境掌握 Java 基本语法掌握条件语句掌握循环语句 学习时间&#…

四川汇昌联信:拼多多网点怎么开?大概需要多少钱?

想要开一家拼多多网点&#xff0c;你肯定很关心需要准备多少资金。下面&#xff0c;我们就来详细解答这个问题&#xff0c;并从多个角度分析开设网点的要点。 一、 开设拼多多网点&#xff0c;首要任务是确定启动资金。根据不同的经营模式和地区差异&#xff0c;成本会有所不同…

WIFI模块的AT指令联网数据交互--第十天

1.1.蓝牙&#xff0c;ESP-01s&#xff0c;Zigbee, NB-Iot等通信模块都是基于AT指令的设计 初始配置和验证 ESP-01s出厂波特率正常是115200, 注意&#xff1a;AT指令&#xff0c;控制类都要加回车&#xff0c;数据传输时不加回车 1.2.上电后&#xff0c;通过串口输出一串系统…