跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ vector 深度解析:从使用到模拟实现

C++ STL 容器 vector 的核心机制涉及基础用法、空间增长策略差异及迭代器失效原理。内容涵盖 resize 与 reserve 对容量的影响,insert 和 erase 操作引发的迭代器失效陷阱,并通过模拟实现剖析底层内存管理细节。结合代码示例提供避坑指南,帮助理解动态数组在内存中的映射逻辑。

草莓泡芙发布于 2026/3/29更新于 2026/10/2490 浏览
C++ vector 深度解析:从使用到模拟实现

vector 的介绍及使用

vector 的介绍

STL 容器的学习通常分为三个境界:能用、明理、能扩展。下面我们以 vector 为例,按照这个思路展开。

vector 的使用

在实际开发中,熟悉常用接口即可。以下是需要重点掌握的构造函数:

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

迭代器并非简单的指针,其内部实现较为复杂。但在存储数据的一片连续空间中,我们可以用指针先简单模拟着理解。

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

关于扩容机制,不同编译器实现可能不同。例如 VS 下的 STL 基本按 1.5 倍增长,而 g++ 下通常是 2 倍。具体增长倍数取决于具体需求定义,不要固化认为都是 2 倍。reserve 只负责开辟空间,如果确定知道需要用多少空间,提前调用可以缓解增容代价;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';
        }
    }
}

如果已经确定 vector 中要存储元素大概个数,可以提前将空间设置足够,避免边插入边扩容导致效率低下:

void TestVectorExpandOpt() {
    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';
        }
    }
}
vector 增删查改
vector 增删查改接口说明
push_back尾插
pop_back尾删
find查找(注意这个是算法模块实现,不是 vector 的成员接口)
insert在 position 之前插入 val
erase删除 position 位置的数据
swap交换两个 vector 的数据空间
operator[]像数组一样访问
vector 迭代器失效问题

迭代器的主要作用是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了。使用一块已经被释放的空间,造成的后果是程序崩溃。对于 vector 可能会导致其迭代器失效的操作有:会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back 等。

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

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

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

erase 删除 pos 位置元素后,pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效。但是,如果 pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是没有元素的,那么 pos 就失效了。因此删除 vector 中任意位置上元素时,vs 就认为该位置迭代器失效了。但是 Linux 下不会挂掉。可以看出不同平台的编译器的设计在文档不做要求的情况下设计可能是不同的。

以下代码的功能是删除 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 下极端。

扩容后的迭代器失效:

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();
    std::cout << "扩容之前,vector 的容量为:" << v.capacity() << std::endl;
    
    // 通过 reserve 将底层空间设置为 100,目的是为了让 vector 的迭代器失效
    v.reserve(100);
    std::cout << "扩容之后,vector 的容量为:" << v.capacity() << std::endl;
    
    // 经过上述 reserve 之后,it 迭代器肯定会失效,在 vs 下程序就直接崩溃了,但是 linux 下不会
    // 虽然可能运行,但是输出的结果是不对的
    while (it != v.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

从上述例子可以看到:SGI STL 中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对。如果 it 不在 begin 和 end 范围内,肯定会崩溃的。在设计上,直接崩溃比较好,因为规避了越界问题。

string 类似情况:

#include<string>
void TestString() {
    std::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);
    }
}

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

vector 在 OJ 中的使用

只出现一次的数字

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

补充位运算小知识:0^任何数字 都是这个数字本身;相同的两个数按位^ 等于 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[] 的形式访问,因为这样便捷。希望大家自己实现一遍上面讲解的 OJ 练习,然后请自行完成下面题目的 OJ 练习,以此增强学习 vector 的使用。

vector 深度剖析及模拟实现

std::vector 的核心框架接口的模拟实现 bit::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());
        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;
};
}

使用 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 是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

动态二维数组理解

以杨辉三角的前 n 行为例:假设 n 为 5。

void test2vector(size_t n) {
    // 使用 vector 定义二维数组 vv,vv 中的每个元素都是 vector<int>
    std::vector<std::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];
        }
    }
}

std::vector<std::vector<int>> vv(n); 构造一个 vv 动态二维数组,vv 中总共有 n 个元素,每个元素都是 vector 类型的,每行没有包含任何元素。使用标准库中 vector 构建动态二维数组时与上图实际是一致的。

目录

  1. vector 的介绍及使用
  2. vector 的介绍
  3. vector 的使用
  4. vector iterator 的使用
  5. vector 空间增长问题
  6. vector 增删查改
  7. vector 迭代器失效问题
  8. vector 在 OJ 中的使用
  9. vector 深度剖析及模拟实现
  10. std::vector 的核心框架接口的模拟实现 bit::vector
  11. 使用 memcpy 拷贝问题
  12. 动态二维数组理解

更多推荐文章

查看全部
  • 自然语言处理在社交媒体分析中的实战应用
  • OpenClaw 集成至飞书构建自动化办公智能体
  • physipy: 使 Python 具有单位感知
  • 大语言模型 (LLM) 入门学习路线图
  • VSCode 集成 Copilot MCP 快速上手指南
  • C++ 继承入门:从概念定义到默认成员函数
  • CoPaw 个人助理部署与定制指南:从零开始打造专属 AI 数字搭档
  • 基于 Numpy 在 MNIST 上实现 CNN 反向传播训练
  • Dify 工作流发布为 MCP Server 实战指南
  • Venera 开源漫画阅读器使用指南与优化技巧
  • YOLO-DRONE:无人机低空巡检模型实测与电力部署解析
  • OpenAI 官方 Prompt 工程指南详解:六大核心原则与实战技巧
  • 基于 FastAPI 自动构建 SSE MCP 服务器
  • PHP 全链路追踪实践:trace_id 生成与透传
  • 在线图书借阅平台的设计与实现
  • Python 基于 OpenCV DNN 的图像风格迁移实战
  • C++ 虚函数与纯虚函数:多态的核心实现原理
  • Rust 异步编程实战:构建高性能网络服务
  • Skills 架构实现企业级 NL2SQL 智能体开发实战
  • SkyWalking Python 应用分布式追踪与埋点实践

相关免费在线工具

  • 加密/解密文本

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