【C++指南】STL容器的安全革命:如何封装Vector杜绝越界访问与迭代器失效?

article/2025/8/17 14:36:13

🌟 各位看官好,我是egoist2023

🌍 种一棵树最好是十年前,其次是现在!

🚀 使用STL的三个境界:能用,明理,能扩展

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!

了解vector常用接口

vector是C++标准模板库中的部分内容,中文偶尔译作“容器”,但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。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);
使用迭代器进行初始化构造

迭代器 

iterator 的使
接口说明
begin + end (重点)
获取第一个数据位置的 iterator/const_iterator , 获取最后一个数据的下
一个位置的 iterator/const_iterator
rbegin rend
获取最后一个数据位置的 reverse_iterator ,获取第一个数据前一个位置的reverse_iterator

容量操作 

容量空间
接口说明
size
获取数据个数
capacity
获取容量大小
empty
判断是否为空
resize (重点)
改变 vector size
reserve (重点)
改变 vector capacity

修改操作

vector 增删查改
接口说明
push_back (重点)
尾插
pop_back (重点)
尾删
find
查找。(注意这个是算法模块实现,不是 vector 的成员接口)
insert
position 之前插入 val
erase
删除 position 位置的数据
swap
交换两个 vector的数据空间
operator[] (重点)
像数组一样访问

vector实现

底层结构

在C语言实现当中,vector实现中并没有迭代器的支持,因此底层结构设计并不复杂。

typedef struct SeqList
{SLDataType* arr;int size;//有效数据个数int capacity;//空间大小
}SL;

为了提供迭代器的支持,可以像指针一样遍历数组,因此对vector的底层封装采用如下。

template<class T>class vector
{
public:typedef T* iterator;typedef const T* const_iterator;//...private://给缺省值iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;
};

迭代器

		iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}


memcpy拷贝问题

void reserve(size_t n)
{if (n > capacity()){size_t oldsize = size();T* tmp = new T[n];memcpy(tmp, _start, sizeof(T) * oldsize);delete[] _start;_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}
}

实际上,上面这段程序在内置类型是不会出问题的,但是针对一些场景(如自定义类型)会报错,如下图所示。

如果vector中存的是自定义类型

问题1:会导致多次析构;

问题2:一个数据的修改会影响另一个 。

问题3:memcpy则只能拷贝每个string,但还是同样指向同一个串。

为了防止浅拷贝问题,如下程序是针对自定义类型的优化。

void reserve(size_t n)
{if (n > capacity()){size_t oldsize = size();T* tmp = new T[n];//memcpy(tmp, _start, sizeof(T) * oldsize); //err只能针对内置类型for (size_t i = 0;i < oldsize;++i){tmp[i] = _start[i]; //内置类型不会有问题//自定义类型调用其=运算符重载函数走深拷贝,防止memcpy出现的问题}delete[] _start;_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}
}
结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为
memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对
指针进行了封装 ,比如: vector 的迭代器就是原生态指针 T* 。因此 迭代器失效,实际就是迭代器
底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间 ,造成的后果是程序崩溃 (
如果继续使用已经失效的迭代器,程序可能会崩溃 )
对于 vector 可能会导致其迭代器失效的操作有:
1. 会引起其底层空间改变的操作,都有可能是迭代器失效 ,比如: resize reserve insert
assign push_back 等。
void insert(iterator pos, const T& x)
{assert(pos < _finish);assert(pos >= _start);if (size() == capacity()){reserve(capacity() == 0 ? 4 : 2 * capacity());}iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = x;++_finish;
}

 在上面这段程序中,由于容量满了需要进行扩容,开辟一段新空间,将旧空间的元素拷贝到新空间上来,并更新_start,_finish,_end_of_storage。但如果迭代器it指向旧空间上的开始位置,此时进行*it会导致野指针解引用问题,这也就是所谓地迭代器失效了。

那该如何解决呢?更新迭代器指向的位置。

void insert(iterator pos, const T& x)
{assert(pos < _finish);assert(pos >= _start);//防止迭代器失效if (size() == capacity()){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}//...
}

更新了迭代器位置后,解引用还是会报错,这是为什么呢?这里看似解决了问题,但是别忘了形参的改变并不能影响实参,即实参中的迭代器依然指向旧空间的位置,依旧会使迭代器失效。那我让形参的改变影响实参可行吗,即加上引用呢?

void insert(iterator& pos, const T& x)

而我们设计初心是想要pos可以随意访问数组中的元素,当想访问数组中的第三个元素时

v.insert(v.begin()+3,3);

 由于是左值引用右值,需要是const左值引用才能引用右值,那么再进行更改。

void insert(const iterator& pos, const T& x)

这里会发现由于const的修饰,会导致insert函数内部是无法修改迭代器pos位置的,因此这种方案也是不可取的。

总之,insert以后,默认迭代器都失效了(尽管在insert函数里修复了迭代器指向位置,但由于形参并不会实参)。


2. 指定位置元素的删除操作 -->  erase
		void erase(iterator pos){assert(pos < _finish);assert(pos >= _start);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;}

这里的删除依然存在着一个隐秘的问题 -->那它又是如何导致的呢?

	auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}++it;}
erase 删除 pos 位置元素后, pos 位置之后的元素会往前搬移,没有导致底层空间的改变,
论上讲迭代器不应该会失效 ,但是:如果 pos 刚好是最后一个元素, 删完之后pos刚好是end
的位置,而end位置是没有元素的,那么pos就失效了(即it和_finish刚好错过了,循环判断依然成立,此时继续执行会出现错误)。
按照上面的说法,那么改下判断条件不就能使it等于_finish了吗?(如下代码所示)但运行之后依然会报错,这是因为 删除 vector中任意位置上元素时,vs就认为该位置迭代器失效了,即在 vs下检查严格 。(Linux 下, g++ 编译器对迭代器失效的检测并不是非常严格,处理也没有 vs 下极端)
	auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}else{++it;}}

因此,使用erase接口时并不能依赖于编译器,应注意需要手动更新迭代器防止迭代器失效问题

在stl库中也是这么解决的。

迭代器失效解决办法:在使用前,对迭代器重新赋值即可
	auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it);}else{++it;}}

3. vector类似,string在插入+扩容操作+erase之后,迭代器也会失效 

总的来说:vector特别需要注意的是在使用insert和erase接口应注意迭代器失效问题,这样才能让我们在使用stl库接口时应对自如。


initializer_list实现

		void push_back(const T& x){if (size() == capacity()){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;_finish++;}vector(initializer_list<T> il){reserve(il.size());for (auto& ch : il){push_back(ch);}}

 


http://www.hkcw.cn/article/uIqvbQdYAw.shtml

相关文章

C++ 异常处理机制与自定义异常体系

目录 1.C语言传统的处理错误的方式 &#x1f60a; 1. 终止程序 2. 返回错误码 3.实际使用中的情况 2. C异常概念&#x1f33c; 2.1 C异常的基本概念 2.2异常的抛出和匹配原则 2.3 异常的重新抛出 2.4 异常安全 2.5 异常规范 3. 自定义异常体系 &#x1f495;&#x…

C++入门看这一篇就够了——超详细讲解(120000多字详细讲解,涵盖C++大量知识)

目录 一、面向对象的思想 二、类的使用 1.类的构成 2.类的设计 三、对象的基本使用 四、类的构造函数 1.构造函数的作用 2.构造函数的特点 3.默认构造函数 3.1.合成的默认构造函数 3.2.手动定义的默认构造函数 四、自定义的重载构造函数 五、拷贝构造函数 1.手动…

【第53节】Windows编程必学之使用C++写exe压缩加密壳

目录 一、实现背景 1.1 前言 1.2 前置知识 1.3 达到目标 二、壳的实现要点 2.1 写壳怎么做 2.2 写壳的困难点 2.3 如何写壳代码 2.4 API函数的调用问题 2.5 重定位问题 2.6 信息交互问题 2.7 调试问题 2.8 关于目标程序的随机基址 2.9 关于目标程序的导入表 2.1…

C++离线查询

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 概念及原理 离线算法( offline algorithms)&#xff0c;离线计算就是在计算开始前已知所有输入数据&#xff0c;输入数据不会产生变化&#xff0c;且在解决一个问题后就要立即得出结果的前提下进行的计算。 通俗的说&a…

金价又涨了!金饰克价涨至1018元,一夜涨14元

美东时间5月23日,国际贵金属期货普遍收涨,COMEX黄金期货涨1.90%,报3357.70美元/盎司,本周累计上涨4.75%。5月24日,国内金饰价格跟涨。周生生足金饰品标价1018元/克,较前一日1004元/克的价格上涨14元/克。责任编辑:zx0002

日本人准备开始吃饲料了?

日本农业水产大臣小泉进次郎十分骄傲地宣布政府将要拿出2021年所产陈米以每5公斤1800日元的价格进行售卖(合人民币差不多1斤大米9块钱)。当地专家吹捧此举将有效缓解日本米荒,并放话越是陈米吃着越香,这下日本人有口福了结果评论区直接翻车了,有网友直接贴出往年饲料米价格…

国际乒联发声明回应选举争议 谴责扰乱行为并重启会议

当地时间29日,国际乒联发布了关于2025年度代表大会期间选举事宜的声明。5月27日,在卡塔尔多哈举行的国际乒联年度股东大会上,因主席选举争议引发混乱,会议最终宣布临时暂停。声明中提到,主席选举结束后,一些既不是会员协会代表也不是执行委员会、理事会、委员会成员或受邀…

胖东来红内裤案宣判:“段某”赔偿40万元 名誉权获法院支持

2025年5月28日,许昌市魏都区人民法院公开审理了许昌市胖东来商贸集团有限公司与段某之间的名誉权纠纷案。法院判决段某在其个人抖音账号“两个小段(小)”发布书面道歉信的视频,并赔偿胖东来公司40万元经济损失。部分人大代表、政协委员、媒体记者、律师代表和企业代表旁听了…

市监总局就毕井泉被查表态 再度引发市场关注

六年多前,毕井泉因长春长生疫苗案从原国家食品药品监督管理总局局长位置引咎辞职的消息震惊了市场;六年多后,他被查的消息再次引发市场的强烈关注。据中央纪委国家监委网站5月29日消息,十四届全国政协常委、经济委员会副主任毕井泉涉嫌严重违纪违法,目前正接受中央纪委国家…

高芙评职业生涯最经典三胜 荣耀时刻回顾

近日,美国网球运动员高芙在法网接受记者采访时,回顾了自己职业生涯中的三场经典胜利。这三场比赛分别是2024年终总决赛争冠战对阵郑钦文、2019年温网第一轮对阵大威廉姆斯以及2023年美网决赛对阵萨巴伦卡。她还特别提到了此前罗马半决赛与郑钦文的那场长达三个半小时的大战,…

女子露营归来脖子惨遭“毁容” 提醒:夏季蚊虫活跃,如遇皮肤瘙痒红肿不能拖

近日,浙江30岁女子小妍露营归来后,颈部便出现刺痛和瘙痒,起初她并未在意。两天后,症状急剧加重——皮肤红肿成片,冒出红色丘疹和水疱,还伴随灼热疼痛。无独有偶,小学生骏骏在户外骑车后,小腿处的皮肤上也出现了个大包。两人来浙江省皮肤病医院就医后,均被确诊为“虫咬…

男子乘火车旅行刷新吉尼斯纪录:24小时内乘火车旅行5887.76公里

近日,吉尼斯世界纪录官网公布了一项纪录——中国男子王冬成功以24小时内5887.76公里的火车旅行距离,刷新了“24小时内乘坐火车旅行最远距离”的吉尼斯世界纪录。▲王冬刷新吉尼斯世界纪录今年39岁的王冬是四川德阳人,12年前在上海求学时的他,就曾因换乘8趟列车回家而走红网…

从外卖APP到网络协议:深入解析UDP及应用层协议

目录 1. 应用层和传输层1.1 开发中常见的自定义协议格式 2. UDP2.1 源端口号及目的端口号2.2 UDP报文长度2.3 UDP校验和(checksum) 3. 基于UDP的应用层协议 关注我&#xff0c;学习更多企业开发和面试内容~ 1. 应用层和传输层 应用层和程序员接触最密切&#xff0c;应用程序&a…

【JavaWeb】基本概念、web服务器、Tomcat、HTTP协议

目录 1. 基本概念1.1 基本概念1.2 web应用程序1.3 静态web1.4 动态web 2. web服务器3. tomcat详解3.1 安装3.2 启动3.3 配置3.3.1 配置启动的端口号3.3.2 配置主机的名称3.3.3 其他常用配置项日志配置数据源配置安全配置 3.4 发布一个网站 4. Http协议4.1 什么是http4.2 http的…

自扶正救生艇,乘风破浪,守护生命

在复杂水域救援中存在显著缺陷。遇巨浪或急流漩涡易倾覆且无法自主复位&#xff0c;使救援人员与被困者陷入二次危险。统计显示&#xff0c;激流救援中近三成五的救援人员伤亡源于船只倾覆后被困。更严重的是&#xff0c;传统救生艇倾覆后常需外部救援力量才能恢复&#xff0c;…

法国7月1日起实施最严户外禁烟令,范围包括这些

法国卫生与家庭部长卡特琳沃特兰表示,法国将在所有儿童可能出入的户外场所禁止吸烟。该禁令将于7月1日生效,范围包括海滩、公园、花园、学校外、公交车站和体育场馆等。责任编辑:zx0002

规定明令禁止自热类食品坐火车既不能带也不能托运

自热米饭发热包因高温爆炸风险被高铁禁止,单独食材包携带需密封完好且车上禁加热。各地规定不一,建议优先选择高铁餐食或非加热速食,出行前确认车站要求。一、发热包为何被禁?安全风险是主因自热米饭的发热包主要成分为镁铝粉、生石灰等,遇水后快速放热,温度可达100℃以上…

贵妇扎堆的“月子中心”IPO 圣贝拉月子中心28天收费13.88万起

核心提示:1.尽管“圣贝拉”定位高端月子服务,28天收费13.88万元起,但一度陷亏损泥潭,2022年经调整净亏损4463万元,高成本结构侵蚀利润,租赁与人力成本合计占销售成本近70%。2.这家“最贵”月子中心的合规风险密集暴露:位于北京的月子中心2021-2022年两次因无证行医被处罚…

(NAT64)IPv6网络用户访问IPv4网络服务器(动态映射方式)

1.实验拓扑 2.配置 [FW1]dis cu 2025-05-29 10:44:44.030 !Software Version V500R005C10SPC300 # sysname FW1 # ipv6 #nat64 prefix 3001:: 96 # interface GigabitEthernet1/0/0undo shutdownip address 1.1.1.1 255.255.255.0 # interface GigabitEthernet1/0/1undo shut…

力扣刷题Day 64:括号生成(22)

1.题目描述 2.思路 回溯&#xff0c;过程当中记录左括号和右括号的数量&#xff0c;以此作为剪枝的依据。 3.代码&#xff08;Python3&#xff09; class Solution:def generateParenthesis(self, n: int) -> List[str]:def backtrack(left_n, right_n, parenthesis):if …