34、STL容器(vector):vector用法、迭代器、容量操作

说实话,vector 是我在 C++ 项目里用得最多的容器,没有之一。你想想看,一个能自动扩容的动态数组,用起来跟普通数组一样快,还不用自己管内存——这谁不爱?

我记得刚入行那会儿,有个同事还在用 new[]delete[] 手动管理数组,结果内存泄漏查了三天。后来我给他推荐了 vector,他拍着桌子说:怎么不早告诉我!

vector 的基本用法

vector 说白了就是一个模板类,定义在 <vector> 头文件里。它的用法非常直观:

#include <vector>
#include <iostream>

int main() {
    // 创建一个空的 vector,存放 int 类型
    std::vector<int> vec;

    // 尾部添加元素
    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);

    // 像普通数组一样访问
    std::cout << vec[0] << std::endl;  // 输出 10
    std::cout << vec.at(1) << std::endl; // 输出 20

    // 遍历
    for (size_t i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

这里有个小细节:vec[2]vec.at(2) 都能访问元素,但 at() 会做边界检查,越界时抛出异常。我个人习惯在调试阶段用 at(),上线后换成 []——毕竟性能差那么一点点。

迭代器:遍历的利器

迭代器是什么?你可以把它想象成一个「智能指针」,专门用来遍历容器里的元素。vector 的迭代器是随机访问迭代器,支持 ++--+n-n 等操作。

std::vector<int> vec = {1, 2, 3, 4, 5};

// 正向迭代器
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " ";  // 输出 1 2 3 4 5
}

// 反向迭代器
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
    std::cout << *it << " ";  // 输出 5 4 3 2 1
}

// 常量迭代器(只读)
for (auto it = vec.cbegin(); it != vec.cend(); ++it) {
    // *it = 10;  // 编译错误!不能修改
    std::cout << *it << " ";
}
我的习惯:能用 auto 就用 auto,省得写一堆 std::vector<int>::iterator 这种又臭又长的类型名。C++11 之后,auto 就是为迭代器而生的。

容量操作:别让 vector 频繁扩容

vector 的底层是一块连续内存。当元素数量超过当前容量时,它会重新申请一块更大的内存,把旧元素搬过去,再释放旧内存。这个过程叫「扩容」。

扩容很贵!我在项目中遇到过一个问题:循环里不断 push_back,结果 vector 反复扩容,性能直接崩了。后来加了一行 reserve,速度提升了 10 倍。

来看几个关键的容量操作:

方法 作用 时间复杂度
size() 返回当前元素个数 O(1)
capacity() 返回当前容量(最多能存多少元素而不扩容) O(1)
reserve(n) 预留至少 n 个元素的空间 O(n)
resize(n) 改变元素个数为 n,多出来的用默认值填充 O(n)
shrink_to_fit() 请求释放多余容量(C++11 起) O(n)
empty() 判断是否为空 O(1)
std::vector<int> vec;
std::cout << "size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;
// 输出: size: 0, capacity: 0

// 预留 100 个元素的空间
vec.reserve(100);
std::cout << "capacity: " << vec.capacity() << std::endl;
// 输出: capacity: 100

// 添加 50 个元素
for (int i = 0; i < 50; ++i) {
    vec.push_back(i);
}
std::cout << "size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;
// 输出: size: 50, capacity: 100(没有扩容!)

// 缩小到实际大小
vec.shrink_to_fit();
std::cout << "capacity: " << vec.capacity() << std::endl;
// 输出: capacity: 50(可能略大于 50,取决于实现)
我曾经踩过的坑:reserve 只预留空间,不创建元素。如果你用 reserve(100) 之后直接 vec[0] = 10,那是未定义行为!必须先 resize 或者 push_back

vector 扩容机制详解

为什么 vector 要按倍数扩容?我简单解释一下:如果每次只多申请一个元素的空间,那插入 n 个元素的时间复杂度是 O(n²)。而按 1.5 倍或 2 倍扩容,均摊下来每个插入操作是 O(1)。

不同的编译器实现不一样:

  • GCC(libstdc++):扩容因子是 2
  • Clang(libc++):扩容因子是 2
  • MSVC:扩容因子是 1.5

嗯,这里要注意:扩容因子不是越大越好。2 倍扩容虽然均摊性能好,但内存浪费也大。1.5 倍扩容更节省内存,但扩容次数会多一些。我个人觉得不用纠结这个,除非你在做嵌入式或者内存极度受限的场景。

知识体系总览

下面这张图帮你理清 vector 的核心知识点:

std::vector 基本用法 push_back / pop_back operator[] / at() 迭代器 begin / end rbegin / rend 容量操作 size / capacity reserve / resize shrink_to_fit 扩容机制:1.5x ~ 2x 倍增 核心原则:提前 reserve,避免频繁扩容

实用技巧与避坑指南

最后分享几个我这些年积累的经验:

  1. 提前预留空间:如果你知道大概要存多少元素,先用 reserve 把空间申请好。我在写网络消息处理模块时,预估每条消息最多 1024 字节,直接 reserve(1024),性能提升很明显。
  2. 迭代器失效:vector 在扩容后,所有迭代器、指针、引用都会失效。我曾经在循环里一边 push_back 一边用迭代器遍历,结果程序崩溃了。记住:插入或删除操作后,之前的迭代器就别用了。
  3. 用 emplace_back 代替 push_back:C++11 引入了 emplace_back,它直接在 vector 内部构造对象,省去了临时对象的拷贝。对于复杂类型,能快不少。
  4. shrink_to_fit 不是强制性的:它只是向系统「请求」释放多余内存,系统不一定会照做。如果你真的需要严格控制内存,不如重新构造一个 vector 然后 swap。
// 强制释放多余内存的经典写法
std::vector<int>(vec).swap(vec);
// 或者 C++11 之后
vec.shrink_to_fit();

总结一下:vector 是 C++ 里最实用的容器之一。掌握它的用法、迭代器和容量操作,能让你写出更高效、更安全的代码。别小看这些基础操作,我在面试时见过不少人连 reserveresize 的区别都说不清楚——嗯,希望你不是其中之一。


公众号:蓝海资料掘金营,微信deep3321