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

C++ vector 容器使用、迭代器失效与模拟实现

C++ vector 容器是动态数组的核心实现。其构造函数、迭代器操作及空间增长机制(resize/reserve)。重点分析迭代器失效场景,包括扩容、插入删除导致的底层指针失效问题,并对比不同编译器行为。通过模拟实现 twg::vector 展示内存管理逻辑,探讨 memcpy 浅拷贝风险及动态二维数组构建方法。结合 OJ 案例演示实际应用,帮助开发者深入理解 STL 容器底层原理与常见陷阱。

技术博主发布于 2026/3/16更新于 2026/5/38 浏览
C++ vector 容器使用、迭代器失效与模拟实现

C++ vector 容器使用、迭代器失效与模拟实现

1. vector 的介绍及使用

1.1 vector 的介绍

vector 是 STL 中的动态数组容器。学习 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

注意: capacity 的代码在 VS 和 g++ 下分别运行会发现,VS 下 capacity 是按 1.5 倍增长的,g++ 是按 2 倍增长的。不要固化认为 vector 增容都是 2 倍,具体增长多少是根据具体的需求定义的。VS 是 PJ 版本 STL,g++ 是 SGI 版本 STL。

  • reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。
  • resize 在开空间的同时还会进行初始化,影响 size。
// 测试 vector 的默认扩容机制 void TestVectorExpand() { size_t sz; std::vector<int> v; sz = v.capacity(); std::cout << "making v 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'; } } }

VS 运行结果通常显示按 1.5 倍方式扩容,Linux 下 g++ 通常按 2 倍方式扩容。

// 如果已经确定 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 可能会导致其迭代器失效的操作有:会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back 等。

案例 1:野指针导致的迭代器失效

#include<iostream>
#include<vector>
using namespace std;

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

案例 2:指定位置元素的删除操作 – erase

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

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

案例 3:删除所有偶数

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

// 错误写法
std::vector<int> v{1, 2, 3, 4};
auto it = v.begin();
while (it != v.end()) {
    if (*it % 2 == 0) v.erase(it);
    ++it;
}

// 正确写法
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;
}

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

1.2.6 vector 在 OJ 中的使用

题目:只出现一次的数字

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

补充位运算小知识:

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

题目:杨辉三角

class Solution {
public:
    std::vector<std::vector<int>> generate(int numRows) {
        std::vector<std::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
#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++; // 修正原代码逻辑错误
        }
        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) {
        // 检查 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);
    // 将二维数组每一行中的 vector<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::vector<twg::vector> vv(n); 构造一个 vv 动态二维数组,vv 中总共有 n 个元素,每个元素都是 vector 类型的,每行没有包含任何元素。

使用标准库中 vector 构建动态二维数组时与上述逻辑一致。

目录

  1. C++ vector 容器使用、迭代器失效与模拟实现
  2. 1. vector 的介绍及使用
  3. 1.1 vector 的介绍
  4. 1.2 vector 的使用
  5. 1.2.1 vector 的定义
  6. 1.2.2 vector iterator 的使用
  7. 1.2.3 vector 空间增长问题
  8. 1.2.4 vector 增删查改
  9. 1.2.5 vector 迭代器失效问题(重点)
  10. 1.2.6 vector 在 OJ 中的使用
  11. 2. vector 深度剖析及模拟实现
  12. 2.1 std::vector 的核心框架接口的模拟实现 twg::vector
  13. 2.2 使用 memcpy 拷贝问题
  14. 2.3 动态二维数组理解
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Python 爬虫入门:Requests 库十个小案例实战
  • 数据结构:顺序表与链表经典算法实战
  • MySQL 日志系统优化:海量日志存储与查询性能提升
  • 大语言模型 LoRA 微调实战指南
  • C++ 继承机制详解:概念、规则与菱形继承
  • 医疗 AI 新范式:数理模型重构传统大模型
  • 结合 cpolar 内网穿透远程部署 Open-Lovable 网页克隆工具
  • AI Skills:前端开发效率新工具
  • Git 安装流程与基础使用指南
  • FastGPT 集成 MCP 协议构建工具增强型智能体指南
  • ASP.NET Core Web API 控制器常用注解属性详解
  • Stable Diffusion 与 I2VGen-XL 图像转视频技术对比
  • Linux 下 OpenClaw 安装、初始化及 Web UI 配置指南
  • C++ 类与对象进阶:初始化列表、静态成员与编译器优化
  • XR 技术概念辨析:OpenVR、OpenXR、SteamVR 与厂商 SDK 差异详解
  • QGroundControl 跨平台安装指南:Windows、macOS、Linux 与 Android 部署
  • AirSim 无人机仿真入门:实现起飞与降落
  • Linux 网络编程实战:HTTP 协议解析与 C++ 服务器实现
  • LangChain 实战:工具调用与结构化输出应用指南
  • 非科班转码者 AI 学习路径:从基础到实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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