6、vector容器(上):vector的构造与赋值、vector的容量与大小、vector的插入与删除

好,咱们今天来聊聊 vector。说实话,vector 是我在 C++ 里用得最频繁的容器,没有之一。无论是刷算法题还是写业务代码,它几乎无处不在。你想想看,一个能自动扩容的动态数组,谁不爱呢?

不过,用归用,很多人其实没搞懂它的底层机制。比如「容量」和「大小」到底有什么区别?为什么插入元素有时候会特别慢?这些问题,咱们今天一次性讲清楚。

vector 容器核心知识体系 构造与赋值 容量与大小 插入与删除 默认构造 带参构造 拷贝构造 assign赋值 size() capacity() resize() reserve() push_back pop_back insert erase 核心:理解 capacity 与 size 的差异是关键

6.1 vector的构造与赋值

先说说构造。vector 提供了好几种构造方式,我平时最常用的是默认构造和带初始大小的构造。

#include <vector>
#include <iostream>
using namespace std;

int main() {
    // 1. 默认构造 —— 空容器
    vector<int> v1;
    
    // 2. 带参构造 —— 10个元素,每个都是5
    vector<int> v2(10, 5);
    
    // 3. 拷贝构造
    vector<int> v3(v2);
    
    // 4. 迭代器范围构造
    int arr[] = {1, 2, 3, 4, 5};
    vector<int> v4(arr, arr + 5);
    
    // 5. 列表初始化(C++11起)
    vector<int> v5 = {1, 2, 3, 4, 5};
    
    return 0;
}

我个人习惯用列表初始化,代码看着清爽。不过要注意,如果你写 vector<int> v(10),它创建的是 10 个默认初始化的元素(int 就是 0),而不是一个容量为 10 的空 vector。这个坑我见过不少人踩过。

赋值方面,除了直接用 = 操作符,还有个 assign 成员函数。它可以把容器里的内容整个替换掉。

vector<int> v;
v.assign(5, 10);      // 现在v里有5个10
v.assign({1, 2, 3});  // 现在v里有1,2,3
小技巧:assign 在需要「复用」一个 vector 变量时特别好用。比如循环里每次要填充不同数据,用 assign 比重新构造效率高。

6.2 vector的容量与大小

这是很多初学者容易混淆的地方。我简单说一下:

  • size():当前容器里有多少个元素
  • capacity():当前容器在不重新分配内存的情况下,最多能装多少个元素
  • empty():判断是否为空
  • max_size():系统允许的最大元素数量(基本用不到)

为什么会有 capacity 这个概念?因为 vector 底层是一块连续内存。如果每次添加元素都重新分配内存,性能会非常差。所以 vector 会预先多申请一些空间。

举个例子:

vector<int> v;
cout << "size: " << v.size() << ", capacity: " << v.capacity() << endl;
// 输出: size: 0, capacity: 0

v.push_back(1);
cout << "size: " << v.size() << ", capacity: " << v.capacity() << endl;
// 输出: size: 1, capacity: 1

v.push_back(2);
cout << "size: " << v.size() << ", capacity: " << v.capacity() << endl;
// 输出: size: 2, capacity: 2

v.push_back(3);
cout << "size: " << v.size() << ", capacity: " << v.capacity() << endl;
// 输出: size: 3, capacity: 4  ← 注意这里!

看到了吗?插入第 3 个元素时,capacity 直接翻倍到了 4。这就是 vector 的扩容策略——通常是 1.5 倍或 2 倍增长,具体看编译器实现。

核心要点:扩容意味着「重新分配内存 + 拷贝所有元素」,这是 O(n) 的操作。如果你提前知道要存多少数据,用 reserve() 预分配空间,能避免多次扩容。

resize()reserve() 的区别,我再说一遍:

  • resize(n):改变 size 为 n。如果 n 大于当前 size,新元素用默认值填充;如果 n 小于当前 size,末尾元素被丢弃。
  • reserve(n):改变 capacity 至少为 n。它不改变 size,只预留空间。

我曾经在项目中遇到过一个性能问题:循环里不断 push_back 几万个元素,程序跑得特别慢。后来加了一行 v.reserve(50000),速度直接提升了十几倍。嗯,这就是预分配的魅力。

6.3 vector的插入与删除

插入和删除操作,是 vector 的「软肋」。因为 vector 是连续存储的,在中间插入或删除元素,需要移动后面的所有元素。

操作 函数 时间复杂度 说明
尾部插入 push_back(val) O(1) 均摊 最常用的插入方式
尾部删除 pop_back() O(1) 只删除,不返回元素
任意位置插入 insert(pos, val) O(n) pos 是迭代器位置
任意位置删除 erase(pos) O(n) 返回下一个元素的迭代器
清空 clear() O(n) size 变 0,capacity 不变

来看代码:

vector<int> v = {1, 2, 3, 4, 5};

// 尾部插入
v.push_back(6);  // v: 1,2,3,4,5,6

// 尾部删除
v.pop_back();    // v: 1,2,3,4,5

// 在开头插入 0
v.insert(v.begin(), 0);  // v: 0,1,2,3,4,5

// 删除第 3 个元素(索引 2)
v.erase(v.begin() + 2);  // v: 0,1,3,4,5

// 删除区间 [1, 3)
v.erase(v.begin() + 1, v.begin() + 3);  // v: 0,4,5

// 清空
v.clear();  // v: empty, capacity 不变
注意:insert 和 erase 会使「被操作位置之后」的所有迭代器、指针、引用失效。如果你在循环里做插入或删除,一定要小心处理迭代器失效的问题。我见过有人因为这个 bug 排查了一整天。

另外,pop_back() 只删除元素,不返回它。如果你想拿到最后一个元素再删除,可以先 v.back()v.pop_back()

说到 clear,它只把 size 置为 0,capacity 保持不变。也就是说,内存并没有还给系统。如果你确定不再需要这块内存,可以用 vector<int>().swap(v) 这个技巧来释放。

vector<int> v(1000, 1);
v.clear();
cout << v.capacity();  // 输出 1000,内存还在

// 释放内存
vector<int>().swap(v);
cout << v.capacity();  // 输出 0

这个 swap 技巧说白了就是:用一个空 vector 和当前 vector 交换内部指针,原来的大块内存就被析构掉了。嗯,有点 hack,但确实好用。

好了,vector 的上半部分就讲到这里。记住一句话:尾部操作是 vector 的强项,中间操作是它的软肋。如果你需要在中间频繁插入删除,那可能要考虑 list 或 deque 了。


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