
观众老爷们大家好 我是邪修KING 许久不更新博客了,欢迎来到晚点C++基础入门博客 C语言到C++基础过渡! 本文属于 C++ STL 从入门到精通系列,vector 是 STL 中最常用、最高频的序列容器,彻底搞懂它是掌握 STL 的第一步!
在 C++ STL 中,vector 是动态数组的代名词 ------ 它封装了连续内存的自动管理,提供了比原生数组更安全、更易用的接口,同时保持了极高的性能。这篇文章将从底层原理、核心接口、STL 生态联系、避坑指南四个维度,一次性讲透 vector 的所有重要知识点,附完整可运行代码与面试高频考点。
一、vector 核心定位与底层原理
1.1 什么是 vector?
vector 是 STL 中的序列容器 ,本质是一个封装好的动态数组模板类 ,具有以下核心特性:
· 连续内存存储 :元素在内存中紧密排列,支持 O (1) 时间复杂度的随机访问
· 自动内存管理 :无需手动 malloc/free,构造 / 析构、扩容 / 缩容自动完成
· 模板通用设计 :支持存储任意类型(int、string、自定义类、智能指针等)
· 头文件:#include
1.2 底层核心结构
vector 的底层实现非常简洁,核心是三个指针(不同编译器命名略有差异,但逻辑一致):
cpp
// 简易底层结构示意
template <typename T>
class vector {
private:
T* _start; // 指向数组起始位置
T* _finish; // 指向最后一个有效元素的下一个位置
T* _end_of_storage; // 指向数组容量的末尾
};
基于这三个指针,我们可以快速计算出 vector 的核心属性:
1. size():有效元素个数 → _finish - _start
2. capacity():当前容量 → _end_of_storage - _start
3. empty():是否为空 → _start == _finish
1.3 扩容机制(面试必考)
当 size() 增长到等于 capacity() 时,vector 会自动触发扩容 ,步骤如下:
1.申请一块更大的新内存 (不同编译器策略不同)
2.将原内存中的元素拷贝 / 移动 到新内存
3.释放原内存
4.更新三个指针指向新内存
扩容策略差异
| 编译器 | 扩容倍数 | 说明 |
|---|---|---|
| VS (MSVC) | 1.5 倍 | 原容量 10 → 扩容后 15,避免内存碎片 |
| GCC/Clang | 2 倍 | 原容量 10 → 扩容后 20,实现简单,性能均衡 |
为什么是 1.5/2 倍,不是固定增长?
因为指数级增长可以将 "扩容 + 拷贝" 的均摊时间复杂度降到 O (1),固定增长会导致均摊复杂度退化为 O (n)。
1.4 与 string 的关系
vector 和 string 是 "近亲" :
·二者底层都是动态连续数组 ,扩容逻辑一致
· string 是 basic_string 的特化,专门处理字符;vector 是通用模板,支持任意类型
· string 有SSO(短字符串优化),vector 没有(直接堆内存存储)
二、vector 核心接口与参数注意事项
2.1 构造函数:5 种高频用法
cpp
#include <vector>
#include <string>
using namespace std;
int main() {
// 1. 默认构造:空 vector
vector<int> v1;
// 2. 填充构造:n 个相同元素
vector<int> v2(5, 10); // 5 个 10:[10,10,10,10,10]
// 3. 范围构造:用其他容器/数组初始化
int arr[] = {1,2,3,4,5};
vector<int> v3(arr, arr+5); // 从数组构造
vector<int> v4(v3.begin(), v3.end()); // 从另一个 vector 构造
// 4. 初始化列表构造(C++11,最常用)
vector<int> v5 = {1,2,3,4,5};
vector<string> v6{"hello", "C++", "STL"};
// 5. 拷贝/移动构造
vector<int> v7 = v5; // 拷贝构造(深拷贝)
vector<int> v8 = move(v5); // 移动构造(C++11,转移内存所有权,v5 变为空)
return 0;
}
注意事项!!!(C++11内容扩展-移动构造!)
· 填充构造的第一个参数是元素个数 ,第二个是元素值 ,不要搞反
· 移动构造后,原对象(v5)处于有效但未定义的状态,不要再访问它的元素
2.2 元素访问:4 种方式
| 方式 | 语法 | 越界检查 | 说明 |
|---|---|---|---|
| 下标访问 | v[i] | 不检查 | 越界触发未定义行为(直接崩溃),性能最高 |
| at () 函数 | v.at(i) | 抛异常 | 越界抛出 out_of_range 异常,更安全 |
| 首尾元素 | v.front() / v.back() | 不检查 | 空 vector 调用会崩溃 |
| 数据指针 | v.data() | - | 返回指向底层数组的指针,兼容 C 语言接口 |
cpp
vector<int> v = {1,2,3,4,5};
cout << v[0] << endl; // 1
cout << v.at(1) << endl; // 2
cout << v.front() << endl; // 1
cout << v.back() << endl; // 5
int* p = v.data();
cout << p[2] << endl; // 3
三、vector 与 STL 生态的重要联系
3.1 与其他序列容器的对比(选择指南)
vector 不是万能的,不同场景要选不同的容器,一张表讲清楚:

3.2 与 STL 算法的完美配合
vector 的迭代器是随机访问迭代器 (STL 中最高级的迭代器类型),支持所有 STL 算法,这是它的核心优势之一。
常用算法示例
cpp
#include <vector>
#include <algorithm> // 算法头文件
#include <iostream>
using namespace std;
int main() {
vector<int> v = {3,1,4,1,5,9,2,6};
// 1. sort:排序(要求随机访问迭代器,list 不能用)
sort(v.begin(), v.end()); // 升序:[1,1,2,3,4,5,6,9]
sort(v.rbegin(), v.rend()); // 降序:[9,6,5,4,3,2,1,1]
// 2. find:查找元素
auto it = find(v.begin(), v.end(), 5);
if (it != v.end()) cout << "找到 5,位置:" << it - v.begin() << endl;
// 3. reverse:反转
reverse(v.begin(), v.end());
// 4. copy:复制元素(配合 back_inserter)
vector<int> v2;
copy(v.begin(), v.end(), back_inserter(v2)); // 自动调用 push_back
// 5. unique:去重(需先排序)
sort(v.begin(), v.end());
auto last = unique(v.begin(), v.end()); // 把重复元素移到末尾
v.erase(last, v.end()); // 删除重复元素
return 0;
}
3.3 与智能指针的结合(现代 C++ 必备)
vector 可以存储智能指针,实现自动内存管理,避免内存泄漏:
1. vector<unique_ptr>:独占所有权
cpp
#include <vector>
#include <memory> // 智能指针头文件
using namespace std;
class MyClass {};
int main() {
vector<unique_ptr<MyClass>> v;
// 错误:unique_ptr 不能拷贝
// unique_ptr<MyClass> up(new MyClass);
// v.push_back(up);
// 正确1:用 move 转移所有权
unique_ptr<MyClass> up(new MyClass);
v.push_back(move(up));
// 正确2:直接 emplace_back(推荐,C++11)
v.emplace_back(new MyClass);
// 正确3:用 make_unique(C++14,最安全)
v.push_back(make_unique<MyClass>());
return 0;
}
2.vector<shared_ptr>:共享所有权
cpp
vector<shared_ptr<MyClass>> v;
auto sp = make_shared<MyClass>();
v.push_back(sp); // 可以拷贝,引用计数+1
四、避坑指南:vector 常见错误与注意事项
1. 永远不要访问空 vector 的 front/back/[]
cpp
vector<int> v;
// v.front(); // 崩溃!
// v[0]; // 崩溃!
if (!v.empty()) { // 先判空
cout << v.front() << endl;
}
//同时也可以在头文件中初始化给值
2. 不要用 reserve 后直接用 [] 访问未初始化的元素
区分reserve和resize!读清楚!!!
cpp
vector<int> v;
v.reserve(10);
// v[0] = 10; // 错误!reserve 只分配内存,不构造元素
v.resize(10); // 正确:resize 会构造元素
v[0] = 10;
3. clear 不释放内存,要用 shrink_to_fit
cpp
vector<int> v(1000);
v.clear(); // size 变为 0,capacity 还是 1000
v.shrink_to_fit(); // 释放多余内存,capacity 变为 0
4. 优先用 emplace_back 代替 push_back
emplace_back 直接在 vector 内存中构造元素,push_back 是先构造临时对象,再拷贝 / 移动到 vector 中,emplace_back 避免了临时对象的开销,性能更高:
cpp
vector<string> v;
v.push_back("hello"); // 构造临时 string("hello"),再拷贝到 vector
v.emplace_back("world"); // 直接在 vector 中构造 string("world")
5. 尽量避免用 vector
vector 是一个特化版本 ,用位压缩 存储每个 bool(1 个字节存 8 个 bool),不是真正的 STL 容器,存在很多坑:
1. 不能取元素的地址(一个位没有地址)
2. 迭代器不是随机访问迭代器
3. 行为和其他 vector 不一致
替代方案 :
1. 若大小固定:用 bitset
2. 若大小动态:用 deque 或 vector
五、总结
1.vector 是 STL 中最常用的序列容器 ,底层是动态连续数组,支持 O (1) 随机访问,尾插尾删效率极高。
2.核心接口要注意参数细节 :reserve 只改 capacity,resize 改 size;emplace_back 优先于 push_back;循环中删除元素要用 erase 的返回值。
3.迭代器失效是最大的坑 :插入、删除、扩容都会导致迭代器失效,要严格遵守失效规则。
4.vector 是 STL 生态的核心 :支持所有 STL 算法,能和智能指针完美结合,是现代 C++ 开发的必备工具。
5.选择容器的核心原则:随机访问多选 vector,中间插入删除多选 list,首尾操作多选 deque。
vector 是 STL 学习的起点,也是面试笔试的高频考点。
本文属于 C++ STL 从入门到精通系列 ,后续会持续更新:deque/list 底层剖析、map/unordered_map 全解、STL 算法深度解析、C++ 面试真题。
关注我,带你从零吃透 STL,少走弯路,快速进阶!
