跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ STL Vector 容器封装:避免越界访问与迭代器失效

综述由AI生成C++ STL Vector 容器涉及底层内存管理与迭代器机制。文章分析了 Vector 常用接口、构造方式及容量操作。重点探讨了 memcpy 浅拷贝在自定义类型下的风险,提出使用深拷贝替代。针对迭代器失效问题,解析了扩容(resize/reserve)、插入(insert)及删除(erase)操作导致指针悬空的原因,并给出更新迭代器指向或接收 erase 返回值等解决方案,确保代码安全性。

极客零度发布于 2026/3/15更新于 2026/4/296 浏览
C++ STL Vector 容器封装:避免越界访问与迭代器失效

了解 vector 常用接口

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

常见构造

构造函数声明接口说明
vector()无参构造
vector(size_type n, const value_type& val)构造并初始化 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 函数里修复了迭代器指向位置,但由于形参并不会实参)。


  1. 指定位置元素的删除操作 --> 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; 
    } 
}
  1. 与 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); 
    } 
}

目录

  1. 了解 vector 常用接口
  2. 常见构造
  3. 迭代器
  4. 容量操作
  5. 修改操作
  6. vector 实现
  7. 底层结构
  8. 迭代器
  9. memcpy 拷贝问题
  10. 迭代器失效问题
  11. initializer_list 实现
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 写小说全流程指南:提示词与工具推荐
  • WhisperLive:实时语音转文字解决方案
  • 基于 FPGA 的高性能 PCI 接口设计与实现
  • 基于 Kaggle 免费环境体验 Stable Diffusion AI 绘画入门
  • AIGC 个性化与定制化内容生成:技术与应用
  • 机器人 DH 参数模型与正运动学
  • FPGA 实现 CIC 抽取滤波器
  • AIGC 版权解析:生成内容归属、侵权认定与保护路径
  • Python-SocketIO 命名空间:构建模块化实时应用
  • 阿里开源 PageAgent:让 AI 住进网页,用自然语言操控界面
  • 主流 AI 编程模型对比与选型指南
  • 用 Prompt 生成正则表达式进行文本匹配
  • JavaScript 中 this 的解析:从 call、bind 到箭头函数
  • AIGC 产品经理转行路径与核心能力体系详解
  • LLaMA-Factory 本地部署:环境配置、CUDA 适配与 WebUI 启动
  • DeerFlow 2.0:字节开源的超级 Agent 框架深度解析
  • Python 爬虫入门实战:Requests、Scrapy 与异步爬取
  • AIGC 时代图文内容社区数据指标体系构建实践
  • 深度 Q 网络与知识图谱融合:映射机制深度解析
  • 大模型时代 AI Agent 技术架构与应用详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online