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

C++ vector 详解:使用、迭代器失效及模拟实现

C++ STL vector 容器涉及动态数组管理、空间增长机制及迭代器失效问题。讲解 vector 常用接口定义、扩容策略(resize/reserve)、增删查改操作细节,重点分析迭代器在扩容或元素删除时的失效原理及跨平台差异。通过模拟实现展示底层内存管理逻辑,探讨 memcpy 浅拷贝风险及动态二维数组构建方法,帮助开发者规避常见陷阱并深入理解容器底层机制。

古灵精怪发布于 2026/3/23更新于 2026/4/212 浏览
C++ vector 详解:使用、迭代器失效及模拟实现

1. vector 的介绍及使用

1.1 vector 的介绍

参考文档:vector 的文档介绍

学习 vector 遵循三个境界:能用,明理,能扩展。

1.2 vector 的使用

在实际使用中熟悉常见接口即可,以下列出重点掌握的接口。

1.2.1 vector 的定义
构造函数声明接口说明
vector() (重点)无参构造
vector (size_type n, const value_type& val = value_type())构造并初始化 n 个 val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造
1.2.2 vector iterator 的使用
iterator 的使用接口说明
begin + end (重点)获取第一个数据位置的 iterator/const_iterator,获取最后一个数据的下一个位置的 iterator/const_iterator
rbegin + rend获取最后一个数据位置的 reverse_iterator,获取第一个数据前一个位置的 reverse_iterator

迭代器不是简单的指针,其实现包装比较复杂,但在存储数据的一片连续空间中可以用指针先简单模拟。

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

在 VS 和 g++ 下运行会发现,VS 下 capacity 是按 1.5 倍增长的,g++ 是按 2 倍增长的。这个问题经常会考察,不要固化认为 vector 增容都是 2 倍,具体增长多少是根据具体的需求定义的。VS 是 PJ 版本 STL,g++ 是 SGI 版本 STL。

reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。 resize 在开空间的同时还会进行初始化,影响 size。


{
     sz;
    std::vector<> v;
    sz = v.();
    std::cout << ;
    ( i = ; i < ; ++i){
        v.(i);
        (sz != v.()){ 
            sz = v.(); 
            std::cout <<  << sz << ;
        }
    }
}
// 测试 vector 的默认扩容机制
void TestVectorExpand()
size_t
int
capacity
"making v grow:\n"
for
int
0
100
push_back
if
capacity
capacity
"capacity changed: "
'\n'

VS 运行结果(按 1.5 倍方式扩容): capacity changed: 1 capacity changed: 2 capacity changed: 3 capacity changed: 4 capacity changed: 6 capacity changed: 9 capacity changed: 13 capacity changed: 19 capacity changed: 28 capacity changed: 42 capacity changed: 63 capacity changed: 94 capacity changed: 141

g++ 运行结果(按 2 倍方式扩容): capacity changed: 1 capacity changed: 2 capacity changed: 4 capacity changed: 8 capacity changed: 16 capacity changed: 32 capacity changed: 64 capacity changed: 128

// 如果已经确定 vector 中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP(){
    std::vector<int> v;
    size_t sz = v.capacity();
    v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
    std::cout << "making bar grow:\n";
    for(int i = 0; i < 100; ++i){
        v.push_back(i);
        if(sz != v.capacity()){ 
            sz = v.capacity(); 
            std::cout << "capacity changed: " << sz << '\n';
        }
    }
}
1.2.4 vector 增删查改
vector 增删查改接口说明
push_back (重点)尾插
pop_back (重点)尾删
find查找。(注意这个是算法模块实现,不是 vector 的成员接口)
insert在 position 之前插入 val
erase删除 position 位置的数据
swap交换两个 vector 的数据空间
operator[] (重点)像数组一样访问
1.2.5 vector 迭代器失效问题。(重点)

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector 的迭代器就是原生态指针 T*。

因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃 (即如果继续使用已经失效的迭代器,程序可能会崩溃)。

对于 vector 可能会导致其迭代器失效的操作有:会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back 等。

1. 野指针导致的迭代器失效

#include<iostream>
using namespace std;
#include<vector>
int main(){
    std::vector<int> v{1,2,3,4,5,6};
    auto it = v.begin();
    // 给 vector 重新赋值,可能会引起底层容量改变
    v.assign(100,8);
    /* 出错原因:以上操作,都有可能会导致 vector 扩容,也就是说 vector 底层原理旧空间被释放掉,
       而在打印时,it 还使用的是释放之间的旧空间,在对 it 迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。
       解决方式:在以上操作完成之后,如果想要继续通过迭代器操作 vector 中的元素,只需给 it 重新赋值即可。 */
    while(it != v.end()){
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

指定位置元素的删除操作–erase

#include<iostream>
using namespace std;
#include<vector>
int main(){
    int a[]={1,2,3,4};
    std::vector<int>v(a, a + sizeof(a)/sizeof(int));
    // 使用 find 查找 3 所在位置的 iterator
    std::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 就认为该位置迭代器失效了。但是 Linux 下不会挂掉。因此可以看出不同平台的编译器的设计在文档不做要求的情况下设计可能是不同的。

以下代码的功能是删除 vector 中所有的偶数,请问那个代码是正确的,为什么?

#include<iostream>
using namespace std;
#include<vector>
int main(){
    std::vector<int> v{1,2,3,4};
    auto it = v.begin();
    while(it != v.end()){
        if(*it %2==0) v.erase(it);
        ++it;
    }
    return 0;
}
int main(){
    std::vector<int> v{1,2,3,4};
    auto it = v.begin();
    while(it != v.end()){
        if(*it %2==0) it = v.erase(it); // erase 返回的删除元素的下一个元素的迭代器
        else ++it;
    }
    return 0;
}

第二个正确。

注意:Linux 下,g++ 编译器对迭代器失效的检测并不是非常严格,处理也没有 VS 下极端。

// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main(){
    std::vector<int> v{1,2,3,4,5};
    for(size_t i = 0; i < v.size(); ++i) cout << v[i] << " ";
    cout << endl;
    auto it = v.begin();
    cout << "扩容之前,vector 的容量为:" << v.capacity() << endl;
    // 通过 reserve 将底层空间设置为 100,目的是为了让 vector 的迭代器失效
    v.reserve(100);
    cout << "扩容之后,vector 的容量为:" << v.capacity() << endl;
    // 经过上述 reserve 之后,it 迭代器肯定会失效,在 vs 下程序就直接崩溃了,但是 linux 下不会
    // 虽然可能运行,但是输出的结果是不对的
    while(it != v.end()){
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

程序输出: 12345 扩容之前,vector 的容量为:5 扩容之后,vector 的容量为:100 0234540912345

这个也就是迭代器失效的第一种案例,即野指针导致的迭代器失效。

// 2. erase 删除任意位置代码后,linux 下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it 的位置还是有效的
#include<vector>
#include<algorithm>
int main(){
    std::vector<int> v{1,2,3,4,5};
    std::vector<int>::iterator it = find(v.begin(), v.end(), 3);
    v.erase(it);
    cout << *it << endl;
    while(it != v.end()){
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

程序可以正常运行,并打印: 4 45

// 3: erase 删除的迭代器如果是最后一个元素,删除之后 it 已经超过 end
// 此时迭代器是无效的,++it 导致程序崩溃
int main(){
    std::vector<int> v{1,2,3,4,5};
    auto it = v.begin();
    while(it != v.end()){
        if(*it %2==0) v.erase(it);
        ++it;
    }
    for(auto e : v) cout << e << " ";
    cout << endl;
    return 0;
}

从上述三个例子中可以看到:SGI STL 中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果 it 不在 begin 和 end 范围内,肯定会崩溃的。

在设计个人觉得还是 VS 的挂掉比较好,因为 VS 直接规避了删除最后一个元素导致的越界问题。

// 4. 与 vector 类似,string 在插入 + 扩容操作 + erase 之后,迭代器也会失效
#include<string>
void TestString(){
    string s("hello");
    auto it = s.begin();
    // 放开之后代码会崩溃,因为 resize 到 20 会 string 会进行扩容
    // 扩容之后,it 指向之前旧空间已经被释放了,该迭代器就失效了
    // 后序打印时,再访问 it 指向的空间程序就会崩溃
    //s.resize(20, '!');
    while(it != s.end()){
        cout << *it;
        ++it;
    }
    cout << endl;
    it = s.begin();
    while(it != s.end()){
        it = s.erase(it);
        // 按照下面方式写,运行时程序会崩溃,因为 erase(it) 之后
        // it 位置的迭代器就失效了
        // s.erase(it);
        // ++it;
    }
}

所以为了使得咱们的代码可以在各个平台都能跑,在使用前,对迭代器重新赋值即可。

1.2.6 vector 在 OJ 中的使用。

只出现一次的数字

class Solution{
public:
    int singleNumber(vector<int>& nums){
        int value = 0;
        for(auto e : nums){
            value ^= e;
        }
        return value;
    }
};

补充位运算小知识:

  1. 0^任何数字 都是这个数字本身
  2. 相同的两个数按位^ 等于 0

杨辉三角

// 涉及 resize / operator[]
// 核心思想:找出杨辉三角的规律,发现每一行头尾都是 1,中间第 [j] 个数等于上一行 [j-1]+[j]
class Solution{
public:
    vector<vector<int>>generate(int numRows){
        vector<vector<int>>vv(numRows);
        for(int i = 0; i < numRows; ++i){
            vv[i].resize(i +1,1);
        }
        for(int i = 2; i < numRows; ++i){
            for(int j = 1; j < i; ++j){
                vv[i][j]= vv[i -1][j]+ vv[i -1][j -1];
            }
        }
        return vv;
    }
};

总结:通过上面的练习我们发现 vector 常用的接口更多是插入和遍历。遍历更喜欢用数组 operator[i] 的形式访问,因为这样便捷。

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

2.1 std::vector 的核心框架接口的模拟实现 twg::vector

【代码实现】:

#pragma once
// vector 的底层模拟实现
#include<iostream>
#include<assert.h>
namespace twg {
template<class T>
class vector{
    typedef T* iterator;
    typedef const T* const_iterator;
public:
    // 默认构造
    vector(){}
    
    // 拷贝构造
    vector(const vector<T>& s){
        T* tmp = new T[s.capacity()];
        // 拷贝数据
        for(int i = 0; i < s.size(); i++){
            tmp[i] = s[i];
        }
        _start = tmp;
        _finish = tmp + s.size();
        _end_of_storage = tmp + s.capacity();
    }
    
    // 迭代器区间拷贝构造
    template<class InputIterator>
    vector(InputIterator first, InputIterator last):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){
        while(first != last){
            push_back(*first);
            ++first;
        }
    }
    
    // 析构
    ~vector(){
        // 释放资源
        if(_start != nullptr){
            delete[] _start;
            _start = _finish = _end_of_storage = nullptr;
        }
    }
    
    // 赋值重载
    vector<T>& operator=( vector<T> s){
        swap(s);
        return *this;
    }
    
    // 其他常用接口
    // 访问符重载
    T& operator[](size_t n){
        assert(n < size());
        assert(n >= 0);
        return *(_start + n);
    }
    const T& operator[](size_t n) const{
        assert(n < size());
        assert(n >= 0);
        return _start[n];
    }
    
    void swap(vector<T>& s){
        std::swap(_start, s._start);
        std::swap(_finish, s._finish);
        std::swap(_end_of_storage, s._end_of_storage);
    }
    
    // reserve
    void reserve(size_t n){
        if(n <= capacity()) return;
        // 不同扩容
        else{
            int oldsize = size();
            T* tmp = new T[n];
            // 拷贝数据
            for(int i = 0; i < oldsize; i++){
                tmp[i] = *(_start + i);
            }
            delete[] _start; // 释放资源
            _start = tmp;
            _finish = tmp + oldsize;
            _end_of_storage = tmp + n;
        }
    }
    
    void push_back(const T& x){
        // 检查空间是否够用
        if(_finish == _end_of_storage){
            // 扩容
            size_t newsize = _finish == nullptr ? 4 : capacity() * 2;
            reserve(newsize);
        }
        // 尾插元素
        *_finish = x;
        ++_finish;
    }
    
    size_t size() const{return _finish - _start;}
    size_t capacity() const{return _end_of_storage - _start;}
    const iterator begin() const{return _start;}
    const iterator end() const{return _finish;}
    
    void print(){
        auto it = begin();
        while(it != end()){
            *it; ++it;
        }
        std::cout << std::endl;
        for(int i = 0; i < size(); i++){
            std::cout << *(_start + i) << " ";
        }
        std::cout << std::endl;
    }
    
    void resize(size_t n, T x){
        if(n <= size()){
            _finish = _start + n;
        } else if(n > size() && n < capacity()){
            int num = n - size();
            while(num--){
                push_back(x);
            }
        } else{
            reserve(n);
            int num = n - size();
            while(num--){
                push_back(x);
            }
        }
    }
    
    void insert(iterator pos, const T& x) // 使用 const 引用
    {
        // 检查 pos 有效性
        assert(pos >= _start && pos <= _finish);
        if(_finish == _end_of_storage){
            // 扩容前计算 pos 的相对位置
            size_t pos_index = pos - _start;
            reserve(capacity() == 0 ? 4 : capacity() * 2);
            pos = _start + pos_index; // 更新 pos 位置
        }
        iterator end = _finish - 1;
        while(end >= pos){
            *(end + 1) = *end;
            --end;
        }
        *pos = x; // 在 pos 位置插入
        ++_finish;
    }
    
    void erase(const iterator& pos){
        auto it = pos + 1;
        while(it != _finish){
            *(it - 1) = *it;
            ++it;
        }
        _finish--;
    }
    
    void pop_back(){
        assert(_finish > _start);
        --_finish;
    }
    
private:
    // 相较 string
    // 其数据是三个迭代器
    iterator _start = nullptr;
    iterator _finish = nullptr;
    iterator _end_of_storage = nullptr;
};
}
2.2 使用 memcpy 拷贝问题

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

int main(){
    twg::vector<std::string> v;
    v.push_back("1111");
    v.push_back("2222");
    v.push_back("3333");
    return 0;
}

问题分析:memcpy 是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。

如果拷贝的是自定义类型的元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为 memcpy 的拷贝实际是浅拷贝。

结论:如果对象中涉及到资源管理时,千万不能使用 memcpy 进行对象之间的拷贝,因为 memcpy 是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

2.3 动态二维数组理解
// 以杨慧三角的前 n 行为例:假设 n 为 5
void test2vector(size_t n){
    // 使用 vector 定义二维数组 vv,vv 中的每个元素都是 vector<int>
    twg::vector<twg::vector<int>> vv(n);
    // 将二维数组每一行中的 vecotr<int> 中的元素全部设置为 1
    for(size_t i = 0; i < n; ++i) vv[i].resize(i + 1, 1);
    // 给杨慧三角出第一列和对角线的所有元素赋值
    for(int i = 2; i < n; ++i){
        for(int j = 1; j < i; ++j){
            vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
        }
    }
}

twg::vectortwg::vector vv(n); 构造一个 vv 动态二维数组,vv 中总共有 n 个元素,每个元素都是 vector 类型的,每行没有包含任何元素,如果 n 为 5 时如下所示。

使用标准库中 vector 构建动态二维数组时与上图实际是一致的。

目录

  1. 1. vector 的介绍及使用
  2. 1.1 vector 的介绍
  3. 1.2 vector 的使用
  4. 1.2.1 vector 的定义
  5. 1.2.2 vector iterator 的使用
  6. 1.2.3 vector 空间增长问题
  7. 1.2.4 vector 增删查改
  8. 1.2.5 vector 迭代器失效问题。(重点)
  9. 1.2.6 vector 在 OJ 中的使用。
  10. 2.vector 深度剖析及模拟实现
  11. 2.1 std::vector 的核心框架接口的模拟实现 twg::vector
  12. 2.2 使用 memcpy 拷贝问题
  13. 2.3 动态二维数组理解
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 使用 frontend-design Skill 提升大模型前端设计审美
  • AIGC 降重实用软件推荐:免费与高性价比工具汇总
  • C++ 关联式容器:map 与 set 详解
  • 基于 Pygame 的 Python 贪吃蛇游戏开发实战教程
  • PyTorch 2.0 深度学习入门与实战指南
C++ 类与对象:封装、实例化与 this 指针详解
  • ComfyUI 与 WebUI 运行 Qwen-Image-2512 对比实测
  • HarmonyOS NEXT 分布式软总线技术架构
  • Android WebView 版本升级方案详解
  • C++11 新特性:可变参数模板、类功能与 STL 变化
  • 基于 Ollama、Llama3.1 与 Open WebUI 搭建本地大模型环境
  • LLM 大模型入门项目推荐:多模态与 RAG 实战指南
  • 本地运行 AI 大模型指南:Ollama 快速上手
  • Java 面试常见题库与答案解析(208 题)
  • 零基础学习 Python 必备开发工具与库指南
  • Pi0 机器人大模型昇腾 A2 环境部署与性能测评
  • VS Code 关闭 Copilot 代码 AI 补全
  • DeepSeek-R1-Distill-Llama-8B 快速部署指南
  • Python 列表与元组的区别及常用操作
  • OpenClaw Web UI 无法访问 Not Found 问题排查
  • 相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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

    • JSON 压缩

      通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online