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

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


🌟 各位看官好,我是egoist2023

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


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

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

了解vector常用接口

vector是C++标准模板库中的部分内容,中文偶尔译作“容器”,但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。

常见构造

(constructor)构造函数声明
接口说明
vector()(重点)
无参构造
vectorsize_type n, const value_type& val = value_type())
构造并初始化nval
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(重点)
改变vectorsize
reserve (重点)
改变vectorcapacity

修改操作

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); } }

 

Read more

尚硅谷2025最新SpringCloud速通-操作步骤(详细)

尚硅谷2025最新SpringCloud速通-操作步骤(详细)

说明:本文是基于【雷丰阳老师:尚硅谷2025最新SpringCloud - 快速通关】进行实践操作,并对雷神的笔记做一个更详细的补充,供大家学习参考,一起加油! 视频地址:1、SpringCloud快速通关_教程简介_哔哩哔哩_bilibili 笔记链接:3. SpringCloud - 快速通关 资料:📎资料.zip(代码+课件+逻辑图) 本人代码:📎springcloud-demo.zip 用于测试API接口的工具:Apipost IDEA自动提示代码插件:通义灵码 目录 目录 springcloud简介 前期准备 建springcloud-demo项目 导依赖 建services模块 导入依赖 建service-order/product模块 nacos - 注册/配置中心 基础入门 注册中心

By Ne0inhk
计算机毕业设计springboot礼物商城的设计与实践 基于SpringBoot的个性化礼品电商平台的设计与实现 基于Java Web的创意礼物在线销售系统的设计与开发

计算机毕业设计springboot礼物商城的设计与实践 基于SpringBoot的个性化礼品电商平台的设计与实现 基于Java Web的创意礼物在线销售系统的设计与开发

计算机毕业设计springboot礼物商城的设计与实践917jxi80(配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。 1. 随着消费升级和社交需求的多元化发展,礼品经济正迎来前所未有的增长机遇。传统礼品采购模式存在选品单一、缺乏个性、购买不便等痛点,难以满足当代消费者对情感表达和独特体验的追求。与此同时,电子商务技术的成熟为礼品行业数字化转型提供了坚实基础,个性化定制与线上购物的深度融合成为行业发展的新趋势。本系统正是在此背景下应运而生,旨在构建一个集礼品展示、个性定制、便捷交易于一体的综合性电商平台,通过技术手段赋能传统礼品行业,提升用户送礼体验,推动礼品消费向品质化、个性化方向发展。 本系统采用SpringBoot作为核心开发框架,结合Vue前端技术实现前后端分离架构,选用MySQL数据库存储业务数据,B/S架构确保系统的可访问性和易维护性。系统围绕用户购物体验和管理者运营需求展开设计,涵盖从商品浏览到订单完成的全流程业务闭环。前台为用户提供礼品信息浏览、个性化搜索筛选、购物车管理、在线支付、订单跟踪

By Ne0inhk
AI Agent 架构详解:感知-决策-执行的三位一体(基于最新研究论文)

AI Agent 架构详解:感知-决策-执行的三位一体(基于最新研究论文)

玄同 765 大语言模型 (LLM) 开发工程师 | 中国传媒大学 · 数字媒体技术(智能交互与游戏设计) ZEEKLOG · 个人主页 | GitHub · Follow 关于作者 * 深耕领域:大语言模型开发 / RAG 知识库 / AI Agent 落地 / 模型微调 * 技术栈:Python | RAG (LangChain / Dify + Milvus) | FastAPI + Docker * 工程能力:专注模型工程化部署、知识库构建与优化,擅长全流程解决方案 「让 AI 交互更智能,让技术落地更高效」 欢迎技术探讨与项目合作,解锁大模型与智能交互的无限可能! AI Agent 架构详解:感知-决策-执行的三位一体 从 ReAct 到 Plan-and-Execute,从单智能体到多智能体协作,AI Agent

By Ne0inhk
Spring Boot自动配置魔法与@EnableAutoConfiguration原理揭秘

Spring Boot自动配置魔法与@EnableAutoConfiguration原理揭秘

目录 🎯 先说说我被自动配置"坑"的经历 ✨ 摘要 1. 自动配置不是魔法,是精妙的设计 1.1 从Spring的"配置地狱"到Spring Boot的"零配置" 2. @EnableAutoConfiguration:自动配置的"开关" 2.1 解剖@SpringBootApplication 2.2 @EnableAutoConfiguration的工作原理 3. SpringFactoriesLoader:自动配置的"寻宝图" 3.1 spring.factories文件的秘密 3.2 自定义自动配置:你也成为"魔法师&

By Ne0inhk