STL精讲:vector容器
大家好,这里是彩妙呀~

在C++的标准模板库(STL)中,vector 绝对是最常用、也最值得初学者优先掌握的容器之一。你可以把它想象成一个“会自己变长的数组”。普通的数组一旦创建,大小就固定了,想添加新元素很麻烦。但 vector 不同,它能在运行时根据需要自动调整大小,帮你管理内存,省去了很多手动操作的麻烦。
作为序列式容器的代表,vector 是学习其他STL容器(如 list, deque)的基础。掌握好 vector 的常用操作(如初始化、遍历、增删改查)和特性(如容量 capacity 与大小 size 的区别),将为你的C++编程打下坚实的基石。接下来,彩妙将带着大家深入了解并掌握这STL中的利器吧~
目录
默认构造函数(empty container constructor)
使用 operator [ ] 运算符重载来使得vector如同int*一样访问元素
vector::size 以及 vector::max_size
查看与改变容量的接口:vector::resize ,vector::reserve与 vector::capacity
查“空”与瘦身:vector::empty与vector::shrink_to_fit
尾插与尾删元素:vector::push_back与vector::pop_back
随意的插入与删除:vector::insert与vector::erase
vector的介绍-参考文档
C++的vector是标准模板库(STL)中的动态数组容器,位于<vector>头文件中。它能够自动管理内存,支持动态扩容,无需手动处理数组大小。vector存储连续的元素,允许通过下标随机访问,效率与普通数组相近。也可以通过百度百科来进行一个了解。
在底层逻辑上,vector是一个顺序表。相比string类来说,少了许多的冗余接口,相对精简一些。
本篇博客会挑一些常用的接口来讲解,如果有需求或者要扩展阅读,可以自己查阅一下参考文档。
由于STL容器中,有多重相似性(如迭代器,相似的遍历之类的),所以我在将第一个string类的链接时,着重吧这些点详细讲了一遍,这里我就不多赘述了。
vector的构建与析构

vector中的构建函数

我们发现,一共有四个接口。但是我们可以阅读文档,发现:C++11中优化了这4个接口,新增了两个接口;C++14中没有增加额外接口,而是对已有接口进行改良。
本篇博客将带着大家学习C++98中的接口,C++11中的优化后续博客会集中讲解。
默认构造函数(empty container constructor)
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec; // 默认构造一个空的 vector
std::cout << "Size: " << vec.size() << std::endl; // 输出 Size: 0
std::cout << "Capacity: " << vec.capacity() << std::endl; // 输出 Capacity: 0
return 0;
}
这个构造函数创建一个空的
vector,初始时没有任何元素。
size()返回 0,表示当前vector中没有元素。
capacity()也可能返回 0,表示当前vector的容量为 0(即没有预分配内存)。
填充构造函数(fill constructor)
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec(5, 10); // 构造一个包含 5 个元素的 vector,每个元素的值为 10
std::vector<int> v2(vec.begin(),vec.end()); //通过迭代器来初始化vector,但迭代器可以指定范围来存数据
std::cout << "Size: " << vec.size() << std::endl; // 输出 Size: 5
for (int i : vec) {
std::cout << i << " "; // 输出 10 10 10 10 10
}
std::cout << std::endl;
return 0;
}
范围构造函数(range constructor)
#include <vector>
#include <iostream>
int main() {
int arr[] = {1, 2, 3, 4, 5};
std::vector<int> vec(arr, arr + 5); // 从数组 arr 的第一个元素到第五个元素构造 vector
std::cout << "Size: " << vec.size() << std::endl; // 输出 Size: 5
for (int i : vec) {
std::cout << i << " "; // 输出 1 2 3 4 5
}
std::cout << std::endl;
return 0;
}
拷贝构造函数(copy constructor)
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec1 = {1, 2, 3, 4, 5}; // 初始化 vec1
std::vector<int> vec2(vec1); // 用 vec1 拷贝构造 vec2
std::cout << "Size of vec1: " << vec1.size() << std::endl; // 输出 Size of vec1: 5
std::cout << "Size of vec2: " << vec2.size() << std::endl; // 输出 Size of vec2: 5
for (int i : vec2) {
std::cout << i << " "; // 输出 1 2 3 4 5
}
std::cout << std::endl;
return 0;
}
析构函数参考文档

一般来说,我们只使用而不去探究其内部工作原理,不怎么要深究这部分知识。
运算符重载:vector::operator=

这个接口底层是套用了默认构造函数与拷贝构造函数,属于一种简便方式:
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec1 = {1, 2, 3, 4, 5}; // 初始化 vec1
std::vector<int> vec2 = vec1; // 用 vec1 拷贝构造 vec2 ,等同于 vec2(vec1)
std::cout << "Size of vec1: " << vec1.size() << std::endl; // 输出 Size of vec1: 5
std::cout << "Size of vec2: " << vec2.size() << std::endl; // 输出 Size of vec2: 5
for (int i : vec2) {
std::cout << i << " "; // 输出 1 2 3 4 5
}
std::cout << std::endl;
return 0;
}
vector的遍历
使用 operator [ ] 运算符重载来使得vector如同int*一样访问元素
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " "; // 输出: 1 2 3 4 5
}
return 0;
}
这中遍历方式是最基础的一种方式,使用数组下标方式访问元素,类似于C风格数组。
- 优点:简单直观,适合需要知道元素索引的场景
- 缺点:需要手动管理索引,容易越界(虽然vector的[]操作不检查边界,但at()会检查)
使用STL经典遍历方式:迭代器(iterator)遍历
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用普通迭代器
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " "; // 输出: 1 2 3 4 5
}
// 使用auto简化(推荐)
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
迭代器是STL的核心概念,提供了统一的容器访问接口
begin()返回指向第一个元素的迭代器,end()返回指向最后一个元素之后的迭代器
*it解引用获取元素值优点:适用于所有STL容器,可以配合算法使用
重要提示:使用
++it(前置递增)而不是it++(后置递增),避免不必要的临时对象
范围for循环(C++11及以上,最推荐的方式)
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 只读遍历
for (int num : vec) {
std::cout << num << " "; // 输出: 1 2 3 4 5
}
// 修改元素(使用引用)
for (auto& num : vec) {
num *= 2; // 修改元素值
}
// 读取修改后的值
for (int num : vec) {
std::cout << num << " "; // 输出: 2 4 6 8 10
}
return 0;
}
C++11引入的特性,语法最简洁,但注意的是:范围for底层也是运用迭代器来进行遍历的,相较于性能效率而言,两者并无区别。这样写只是提高了代码的可读性。
读取元素时使用
const auto&避免拷贝(推荐)修改元素时使用
auto&(引用)最推荐的遍历方式,现代C++编程的首选
vector中容器的接口

vector::size 以及 vector::max_size

这个接口可以查看我们vector中实际存放的元素数量,来看代码:
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> box{1, 2, 3}; // 装了3个整数
cout << box.size(); // 输出:3
return 0;
}

这个接口建议查看 vector理论上能装的最大元素数量(受系统 / 编译器限制的上限,比如内存、数据类型大小)。但这个接口几乎用不上,所以不在展示。
查看与改变容量的接口:vector::resize ,vector::reserve与 vector::capacity

这个接口可以主动修改vector中实际存放的元素数量(可以 “加位置” 也可以 “减位置”)。
使用方法:
resize(n):把元素数量改成 n。
若 n > 原 size:新增位置填充默认值(int 填 0、string 填空串);
若 n < 原 size:删掉后面的元素。
resize(n, val):新增位置填充指定值 val(更灵活)。
vector<int> box{1, 2}; // 初始size=2
box.resize(4); // size变4,新增位置填0 → {1,2,0,0}
box.resize(2); // size变2,删掉后面元素 → {1,2}
box.resize(5, 9); // size变5,新增位置填9 → {1,2,9,9,9}

这个接口可以提前申请指定大小的存储空间(只改 capacity,不改 size),避免频繁扩容导致性能损耗。相当于扩容(在vs编译器下,扩容倍率是按照1.5倍来进行扩容(向上取整)g++中采用的是2倍扩容)
注意:reserve(n),仅当 n > 当前 capacity 时生效;若 n < 当前 capacity,无任何变化。
vector<int> box;
box.reserve(100); // 提前申请能装100个元素的空间
cout << box.size(); // 输出:0(没装任何东西)
cout << box.capacity(); // 输出:100(能装100个)

这个接口可以查看 vector实际分配的存储空间能装多少元素(盒子本身的容量,不是实际装的数量)
size是 “装了多少”,capacity是 “能装多少”。vector 为了效率,会提前多分配空间,避免每次加元素都重新分配内存。
vector<int> box;
box.push_back(1); // 装1个元素:size=1,capacity=1(不同编译器可能不同)
box.push_back(2); // 装2个元素:size=2,capacity=2
box.push_back(3); // 装3个元素:size=3,capacity=4(自动扩容,比如成倍扩)
cout << box.capacity(); // 输出:4
查“空”与瘦身:vector::empty与vector::shrink_to_fit

这个接口可以检查 vector 里是否为空(有没有装任何元素),返回值为bool值。
vector<int> box;
cout << boolalpha; // 让cout输出true/false而不是1/0
cout << box.empty(); // 输出:true
box.push_back(1);
cout << box.empty(); // 输出:false

这个接口可以把 vector 的存储空间 瘦身 到和实际元素数量一致(本质上是让 capacity = size,从而释放空间),释放多余的内存。
vector<int> box;
box.reserve(100); // capacity=100,size=0
box.push_back(5); // capacity=100,size=1
box.shrink_to_fit(); // 瘦身:capacity=1,size=1
cout << box.capacity(); // 输出:1
小总结:
- size/empty:关注 “实际装了多少”——size 查数量,empty 判空(优先用 empty);
- capacity/reserve/shrink_to_fit:关注 “分配了多少空间”——capacity 查容量,reserve 提前扩容,shrink_to_fit 释放多余空间;
- resize/max_size:resize 改实际元素数,max_size 是理论上限(几乎不用)。
vector中修改类型(modifiers)的接口

清空,再重新装填:vector::assign

把 vector 里现有的所有东西全清掉,重新装一批新元素(新东西)(相当于 “重置” 整个盒子的内容)。
这个接口有两个函数重载:
assign(n, val):装 n 个值为 val 的元素;assign(起始迭代器, 结束迭代器):从其他容器 / 数组里复制一段元素过来;
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> box{1,2,3}; // 初始:{1,2,3}
box.assign(2, 9); // 清空后装2个9 → {9,9}
vector<int> other{4,5,6};
box.assign(other.begin(), other.end()); // 复制other的所有元素 → {4,5,6}
return 0;
}
尾插与尾删元素:vector::push_back与vector::pop_back

这个接口可以往 vector 的最后面塞一个元素。而且会自动扩容(相当于调用reserve()接口):
vector<int> box{1,2};
box.push_back(3); // 尾部加3 → {1,2,3}
box.push_back(4); // 尾部加4 → {1,2,3,4}

这个接口只做一件事情:把 vector最后一个元素拿走(只删末尾,不删中间),操作只改变vector的大小,而不改变容量。
vector<int> box{1,2,3,4};
box.pop_back(); // 删掉最后一个4 → {1,2,3}
box.pop_back(); // 删掉最后一个3 → {1,2}
随意的插入与删除:vector::insert与vector::erase

这个接口可以在 vector任意位置插入元素(比如开头、中间),插入位置后的元素会自动往后挪。但因为要牵扯到元素的移动,使用效率会有损失。
常见的三种使用方式:
insert(位置迭代器, val):在指定位置插 1 个val;insert(位置迭代器, n, val):在指定位置插n个val;insert(位置迭代器, 起始迭代器, 结束迭代器):插一段其他元素;
vector<int> box{1,4};
// 在第二个位置(begin()+1)插一个2 → {1,2,4}
box.insert(box.begin()+1, 2);
// 在第三个位置(begin()+2)插2个3 → {1,2,3,3,4}
box.insert(box.begin()+2, 2, 3);

这个接口可以删除 vector指定位置 / 指定范围的元素,删除后后面的元素会自动往前挪(同样有效率损失)。
常见的两种使用方式:
erase(位置迭代器):删除单个元素;erase(起始迭代器, 结束迭代器):删除一个范围的元素;
vector<int> box{1,2,3,4,5};
box.erase(box.begin()+2); // 删除第三个元素(3)→ {1,2,4,5}
// 删除第2到第4个元素(2,4)→ {1,5}(注意:结束迭代器是“删到但不包含”)
box.erase(box.begin()+1, box.begin()+3);
交换两个vector:vector::swap

这个接口把两个 vector 的所有内容互换(包括里面的元素、size、capacity),效率极高(只是换个 “盒子标签”(类比指针的交换),不拷贝元素)。
vector<int> box1{1,2}; // size=2, capacity=2
vector<int> box2{3,4,5}; // size=3, capacity=3
box1.swap(box2);
// 互换后:box1={3,4,5},box2={1,2}
清空vector:vector::clear

这个接口把 vector 里的所有元素清空(盒子里空了),但盒子本身的容量(capacity)不变(盒子大小还在)。
vector<int> box{1,2,3};
box.clear(); // 清空后size=0,capacity仍为3(盒子还能装3个)
cout << box.size(); // 输出:0
cout << box.capacity(); // 输出:3
emplace是C++11中提出的新玩意,后续会将。
vector中的运算符重载:
(1) template <class T, class Alloc> bool operator== (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);(2) template <class T, class Alloc> bool operator!= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);(3) template <class T, class Alloc> bool operator< (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);(4) template <class T, class Alloc> bool operator<= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);(5) template <class T, class Alloc> bool operator> (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);(6) template <class T, class Alloc> bool operator>= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

这类运算符重载可以实现两个vector的大小比较,但大多数的运算符重载都用了复用:
| operation | equivalent operation |
|---|---|
a!=b | !(a==b) |
a>b | b<a |
a<=b | !(b<a) |
a>=b | !(a<b) |
零星知识点
- vector不支持流插入与流提取,大多数情况下都是使用一个临时变量int x来进行vector的尾插(或者使用C语言中数组输入的风格)。这里不是因为实现麻烦,而是因为输入输出不固定,可以多样化来输入输出。
- vector可以使用其他类型初始化:例如vector<char> , vector<double>,甚至可以用类类型初始化,但是不保证其可以满足预期(例如vector<char>不会自动补齐'\0',string类可以自动补齐,且可以实现编码需求)。
本篇到这里就结束了,喜欢文章的小伙伴可以关注一下彩妙,我们下一篇再见~

更多推荐
所有评论(0)