【C++】vector类详解

620 阅读5分钟

一、什么是 vector?为什么要用它?

vector 是 C++ 标准库中提供的一个 动态数组容器,属于 <vector> 头文件。它的本质是:

一个可以自动扩容的数组,支持高效的随机访问和灵活的元素插入与删除。

相比 C 风格的数组,vector 有以下额外特点:

  • 自动管理内存
  • 内置边界检查
  • 丰富的成员函数
  • 与 STL 算法无缝配合

因此,在现代 C++ 开发中,vector 是最推荐的数组替代品之一。


二、vector 的核心特点

特性描述
动态扩容不需要提前知道数组大小,push_back() 会自动增加容量
支持随机访问可以像数组一样通过 v[i]at(i) 访问元素
支持迭代器可与 begin()end() 配合使用 STL 算法
数据连续存储元素在内存中连续排列,可与 C 数组互通(如传递给 C 风格函数)
类型安全支持泛型类型 vector<T>,保证类型一致性

三、vector 的基本使用方法

1. 引入头文件

#include <vector>

2. 创建与初始化

std::vector<int> v1;                         // 空 vector
std::vector<int> v2(5);                      // 长度为5,默认值为0
std::vector<int> v3(5, 10);                  // 长度为5,值为10
std::vector<int> v4 = {1, 2, 3, 4};          // 列表初始化(C++11)

3. 常用操作

v.push_back(10);        // 末尾添加元素
v.pop_back();           // 删除末尾元素
v.size();               // 当前元素数量
v.empty();              // 是否为空
v.clear();              // 清空所有元素
v.front();              // 第一个元素
v.back();               // 最后一个元素
v[i];                   // 随机访问(不检查越界)
v.at(i);                // 随机访问(检查越界)

四、内存管理与扩容机制(理解 vector 的关键)

vector 在内部维护三个指针:

  • begin:指向数据起始位置
  • end:指向数据末尾
  • capacity_end:指向当前分配内存的末尾

当你调用 push_back() 添加元素时:

  • 如果容量足够,仅构造新元素
  • 如果容量不足,会自动重新分配更大的内存空间,并复制旧数据

通常扩容是按 1.5~2 倍增长,因此在大量插入前可使用 reserve(n) 提前分配空间,避免多次扩容带来的性能损耗。

鉴于vector是动态数组,那么动态分配内存就难免会产生内存碎片,这也是无法避免的问题,那么我们如果要尽量减少内存碎片,就应该提前预算好自己需要的空间。


五、迭代器的使用

vector 支持标准 STL 迭代器:

for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << std::endl;
}

也可以使用 C++11 范围 for:

for (int x : v) {
    std::cout << x << std::endl;
}

甚至可以配合算法库:

std::sort(v.begin(), v.end());

当然,使用迭代器要注意避免迭代器失效问题

什么是迭代器失效?

迭代器失效指的是:原来有效的迭代器,在容器结构或数据发生变化后,变得无效,不能再安全地使用,否则会导致未定义行为。

vector它维护了几个内部成员:

  • T* data:数据指针,指向第一个元素
  • size_t size:当前元素个数
  • size_t capacity:当前分配的容量

所以,如果底层的 data 指针发生了变化(比如扩容重新分配内存),那么旧的迭代器(指针)就不再指向有效内存,也就失效了


有效的情况(不会失效):

  • 对 vector 的元素进行修改:vec[i] = 5; 不会使迭代器失效。
  • 不涉及插入或删除操作。

会导致失效的操作:

操作是否会导致失效说明
push_back可能如果导致扩容,所有迭代器失效
insert插入点之后的所有迭代器失效,若扩容则全部失效
erase被删除元素之后的所有迭代器失效
clear所有迭代器失效
resize会影响所有元素和迭代器,尤其是缩小时
reserve可能如果容量变化,会导致所有迭代器失效

示例代码

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3};
    auto it = v.begin(); // 指向1

    v.push_back(4);      // 如果导致扩容,it 将失效

    std::cout << *it << std::endl; //  未定义行为,可能崩溃
}

如何避免迭代器失效?

  • 在容器修改后重新获取迭代器。
  • 使用 返回值 重新赋值:
auto it = v.erase(v.begin()); // 返回值是新位置的迭代器
  • 使用 std::liststd::deque,它们在某些操作中不会使迭代器失效。
  • 使用 reserve() 提前分配足够空间,减少扩容次数。

六、vector 与指针 / 数组兼容性

由于 vector 内存是连续的,你可以将其数据传递给接受指针的函数:

void process(int* data, size_t len);

std::vector<int> v = {1, 2, 3, 4};
process(v.data(), v.size());

这让 vector 成为既现代又兼容的容器选择。


七、拷贝、赋值、引用行为

vector 支持拷贝构造、赋值运算,底层会进行深拷贝

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1; // 拷贝数据,不共享内存

传参时建议使用 引用或 const 引用,避免不必要的复制:

void print(const std::vector<int>& v);

八、常见用法

技巧说明
reserve(n)提前分配内存,避免多次扩容
shrink_to_fit()将 capacity 减少到 size(不一定成功)
emplace_back()直接原地构造对象,性能优于 push_back()
与结构体结合使用vector<Person> 可以存储自定义类型
std::vector<std::vector<T>>可构建二维数组(如矩阵、图等结构)

九、vector 的使用场景

适合:

  • 元素数量不确定的线性结构
  • 需要快速随机访问的场景
  • 需要与 C 接口互通的数组型数据

不适合:

  • 频繁在中间插入/删除(建议使用 listdeque
  • 数据量特别大但很稀疏(建议使用 mapunordered_map
  • 固定长度、追求极致性能的低层嵌入式场景(建议使用原始数组)

十、总结

std::vector 是 C++ 中最常用、最稳定、最强大的容器之一。它不仅是数组的现代替代品,更是构建所有线性结构的基础。

大家快用起来吧~