vector是C 标准模板库STL的一个容器它本质是一个封装了动态内存管理的数组既保留了原生数组随机访问的高效性又解决了固定长度、手动管理内存的痛点。一vector是什么std::vector是 STL 中的顺序容器底层在堆上开辟连续的内存空间存储元素支持动态自动扩容可以理解为「C 版的动态数组」。它的核心特性可以总结为四点1,连续内存存储所有元素在内存中紧密连续排列支持 O (1) 时间复杂度的随机访问原生指针可直接操作元素兼容所有 C 风格接口。2,动态自动扩容无需提前声明固定长度当元素数量超过当前内存容量时会自动申请更大的内存并迁移数据全程无需开发者手动管理。3,尾操作高效中间操作低效尾部插入 / 删除的均摊时间复杂度为 O (1)头部或中间位置的插入 / 删除需要移动后续所有元素时间复杂度为 O (n)。4,类型安全基于模板实现编译期完成类型校验相比原生 C 数组更安全不易出现类型不匹配的问题。二vector的基础用法2.1构造和初始化vector 提供了多种构造方式覆盖不同初始化场景#include vector #include iostream int main() { // 1. 空 vector无元素size0capacity0 std::vectorint v1; // 2. 构造包含 5 个元素的 vector元素为默认值int 为 0 std::vectorint v2(5); // 3. 构造包含 5 个元素每个元素值都是 10 std::vectorint v3(5, 10); // 4. 拷贝构造用另一个 vector 完整复制 std::vectorint v4(v3); // 5. 列表初始化C11 起最直观的写法 std::vectorint v5{1, 2, 3, 4, 5}; // 6. 迭代器范围初始化 std::vectorint v6(v5.begin(), v5.begin() 3); // 元素为 1,2,3 return 0; }2.2 元素访问vector 提供了多种访问元素的方式各有适用场景std::vectorint v{10, 20, 30, 40, 50}; // 1. 下标访问 []无越界检查越界属于未定义行为性能最高 int a v[2]; // 30 // 2. at() 方法越界时抛出 std::out_of_range 异常更安全 int b v.at(2); // 30 // 3. 访问首尾元素 int first v.front(); // 10 int last v.back(); // 50 // 4. 获取底层原生指针可对接 C 语言接口 int* ptr v.data();2.3 常用增删操作std::vectorint v{1, 2, 3}; // 尾部插入 v.push_back(4); // 尾部插入元素 4 v.emplace_back(5); // 尾部直接构造元素性能更优 // 尾部删除 v.pop_back(); // 删除最后一个元素无返回值 // 指定位置插入 v.insert(v.begin() 1, 10); // 在第2个位置插入 10 // 指定位置删除 v.erase(v.begin() 1); // 删除第2个元素 // 清空所有元素 v.clear();2.4 三种遍历方式std::vectorint v{1, 2, 3, 4, 5}; // 1. 下标遍历适合需要索引的场景 for (size_t i 0; i v.size(); i) { std::cout v[i] ; } // 2. 迭代器遍历容器通用的遍历方式 for (auto it v.begin(); it ! v.end(); it) { std::cout *it ; // 3. 范围 for 循环C11 起语法最简洁 for (auto num : v) { std::cout num ; }三底层原理vector 是怎么工作的只停留在「会用」层面很容易踩坑。理解 vector 的底层实现是写出高效、安全代码的关键。3.1 核心数据结构三个指针1_start指向内存块的起始位置也就是第一个元素的地址2_finish指向最后一个有效元素的下一个位置3_end_of_storage指向整块分配内存的末尾位置通过这三个指针就能推导出两个核心属性1size()当前有效元素个数 _finish - _start。这个计算基于指针算术时间复杂度为 O(1)。size()返回的是容器中实际存储的元素数量而不是内存容量。当容器为空时_start _finishsize()返回 0。需要注意的是size()返回的类型是size_t无符号整数因此在循环条件中与有符号数比较时要注意类型转换问题。此外size()是成员函数调用时不会改变容器状态是 const 成员函数。2capacity()当前内存总共能容纳的元素数 _end_of_storage - _start3.2扩容机制全流程当我们调用push_back/emplace_back时如果size() capacity()说明当前内存已经满了就会触发自动扩容完整流程分为四步1,申请新内存向系统申请一块更大的连续内存扩容倍率由编译器实现决定 ——GCC 标准库为 2 倍扩容MSVC 为 1.5 倍扩容。2,迁移元素将原内存中的所有元素通过拷贝构造或移动构造C11 起复制到新内存中。3,释放旧内存调用原内存中所有元素的析构函数再把整块旧内存归还给系统。4,更新指针将三个指针全部更新指向新内存的对应位置。这也是为什么扩容后所有指向原 vector 的迭代器、指针、引用都会全部失效 —— 因为原内存已经被释放了。四高频接口4.1reserve和resize:这是最容易混淆的两个接口核心区别在于操作的对象不同1reserve(n)操作容量capacity只预分配内存不创建、不销毁任何元素不会改变size()。它的唯一作用是提前申请内存避免后续插入时频繁扩容。2resize(n, val)操作有效元素数量size会真正构造或销毁元素改变size()如果 n 大于当前 capacity会自动触发扩容。举个直观的例子std::vectorint v; v.reserve(10); // capacity10size0没有任何有效元素 v[0] 1; // 错误越界访问未定义行为 v.resize(10); // capacity10size10有10个值为0的元素 v[0] 1; // 正确push_back vs emplace_back性能差异后面的文章中我会讲到。4.2clear()会释放内存吗?答案是没有。clear()只会调用所有元素的析构函数将size()置为 0但底层内存不会释放capacity()保持不变。如果 vector 曾经装过大量数据后续不再使用直接调用clear()会造成内存闲置。五vector最容易犯的错误5.1 迭代器失效最经典的坑迭代器失效是 vector 开发中最常见的 bug 来源主要有两类场景1触发扩容的插入操作所有迭代器、指针、引用全部失效2中间位置的插入 / 删除操作点之后的所有迭代器、指针、引用全部失效。最典型的错误写法 —— 循环中删除元素很多人误以为reserve(n)之后就有了 n 个元素可以直接用下标赋值这是典型的错误。reserve只分配了内存没有构造元素此时size()仍然是 0下标访问属于越界行为会导致未定义错误。// 错误写法erase 后迭代器失效后续 是未定义行为 for (auto it v.begin(); it ! v.end(); it) { if (*it target) { v.erase(it); } } // 正确写法使用 erase 返回的下一个有效迭代器 for (auto it v.begin(); it ! v.end(); ) { if (*it target) { it v.erase(it); } else { it; } }5.2reserve后直接下标访问很多人误以为reserve(n)之后就有了 n 个元素可以直接用下标赋值这是典型的错误。reserve只分配了内存没有构造元素此时size()仍然是 0下标访问属于越界行为会导致未定义错误。5.3忽略扩容代价频繁 push_back如果知道最终要插入的元素数量却不提前reserve会导致 vector 多次扩容、多次拷贝全量数据在数据量大时会带来非常明显的性能损耗。六性能优化最佳实践1. 预知数据量时提前 reserve 预分配这是性价比最高的优化手段。如果能预估元素的大致数量提前调用reserve()一次性分配足够内存可以彻底避免中途扩容带来的拷贝开销。2. 避免在头部 / 中间频繁插入删除vector 的优势在尾部操作头部 / 中间插入需要移动大量元素。如果业务场景需要频繁在中间增删应该优先选择std::list。最后vector在最常见的场景随机访问、尾部增删下做到了极致性能同时兼顾易用性。理解它的底层逻辑是为了写出更高效、更安全的代码知道什么时候该预分配知道哪些操作会导致迭代器失效知道它的边界在哪里才能真正发挥 C 容器的威力。