vector:最常用的动态数组
这是什么:vector——C++ STL 里最常用的容器,一个会自己长大的动态数组,放多少都不会溢出。
为什么用:上一章认识了 STL 三件套,该请出出场率最高的这位了。普通数组得提前定好长度,元素一多就装不下;vector 不用改代码就能自动伸缩,堪称 C++ 版的 Python 列表。
如何用:一个 std::vector<int> 声明就能起步,本章从 push_back 增删、size() 取长度,一路讲到两种遍历和越界。
定义与增删
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {10, 20, 30}; // 定义时给初始值
v.push_back(40); // 尾部加一个 40
v.push_back(50); // 尾部加一个 50
v.pop_back(); // 尾部删一个(删掉的正是 50)
std::cout << "个数:" << v.size() << std::endl;
std::cout << "第 1 个:" << v[0] << std::endl;
std::cout << "最后一个:" << v[v.size() - 1] << std::endl;
return 0;
}
你会看到:
个数:4
第 1 个:10
最后一个:40
push_back 加在尾部,pop_back 删掉尾部,size() 报个数,v[i] 按下标取值——下标从 0 起,和 Python 列表一样。
两种遍历
挨个处理叫遍历,vector 有两种常用写法:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {10, 20, 30, 40};
for (int i = 0; i < (int)v.size(); i++) { // 写法一:下标 for
std::cout << v[i] << " ";
}
std::cout << std::endl;
for (int x : v) { // 写法二:range-for
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
你会看到:
10 20 30 40
10 20 30 40
要修改元素、关心"第几个",用下标 for;只读一遍,range-for 更省心。小提示:size() 返回的是无符号数,直接和 int 比较编译器会嘀咕,加个 (int) 转换就清净了。
修改元素
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {10, 20, 30};
v[1] = 99; // 把第 2 个改成 99
std::cout << v[1] << std::endl; // 99
// 插入、删除中间元素(较慢,因为要搬移后续元素)
v.insert(v.begin() + 1, 55); // 在下标 1 位置插入 55
v.erase(v.begin() + 2); // 删除下标 2 的元素
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
你会看到:
99
10 55 30
自动扩容的直觉
vector 内部其实管着一小块连续内存。装满了还 push_back?它会悄悄换个更大的房子:申请一块差不多翻倍的空间,把老数据整体搬过去,再继续装。所以"会自己长大"不是魔法,是偶尔搬一次家——大部分时候 push_back 都飞快。
常用操作速查
| 操作 | 干什么 | 备注 |
|---|---|---|
v.push_back(x) |
尾部加 x | 最常用的加法 |
v.pop_back() |
尾部删一个 | 删之前确认非空 |
v.size() |
元素个数 | 无符号数 |
v[i] |
取第 i 个 | 下标从 0 起,越界会出事 |
v.empty() |
是否为空 | 比 size() == 0 好读 |
v.insert(pos, x) |
在位置插入 | 较慢,会搬移元素 |
v.erase(pos) |
删除位置元素 | 较慢,会搬移元素 |
v.clear() |
清空所有元素 | 个数变 0 |
记忆口诀
vector 是长大的数组:尾进尾出 push/pop,size 数个数,下标从 0 起。
❓ 常见问题
Q1:v[i] 越界会怎样?
v[i] 不做边界检查,越界是未定义行为,可能打印乱数、可能崩溃。想安全访问用 v.at(i),越界会抛出异常(但性能略慢)。
Q2:vector 能存不同类型的数据吗?
不能。std::vector<int> 只能存 int,std::vector<std::string> 只能存 string。C++ 是静态类型,一个容器里元素类型必须一致。
Q3:push_back 很慢吗?
大部分时候很快(摊还 O(1))。偶尔触发扩容时会慢一点(要申请新内存、拷贝旧数据),但这种情况很少。
⚠️ 新手翻车现场
翻车一:v[v.size()] 越界
// 错误:size 是个数,最大有效下标是 size()-1
std::cout << v[v.size()] << std::endl;
问题:v.size() 是元素个数,比如 4 个元素,有效下标是 0~3,v[4] 已经越界。
修正:取最后一个用 v[v.size() - 1]。
翻车二:pop_back() 对空 vector 操作
std::vector<int> v; // 空
v.pop_back(); // 未定义行为,可能崩溃
问题:空 vector 没有尾部元素可删。
修正:删之前判空:if (!v.empty()) v.pop_back();
🎯 什么时候用 vector
- 需要一个可变长度的数组
- 主要在尾部添加/删除元素
- 需要按下标快速随机访问元素
- 元素类型统一
📝 本章小结
vector是会自动长大的动态数组,下标从 0 起push_back尾部加,pop_back尾部删,size()报个数- 遍历:下标 for(可修改)或 range-for(只读更省心)
v[i]不做越界检查,越界未定义行为;安全访问用v.at(i)- 扩容是自动的,大部分
push_back都很快 - 记忆口诀:vector 是长大的数组:尾进尾出 push/pop,size 数个数,下标从 0 起。
动手改
- 建一个
std::vector<std::string>(记得#include <string>)装 3 种水果,加一种、删一种,打印 size 和剩下的全部元素 - 用下标 for 把每个元素乘 2(
v[i] = v[i] * 2;),再打印整个 vector - 试着打印
v[v.size()]——这就是越界,程序可能当场崩溃;取"最后一个"的正确写法是v[v.size() - 1] - 用
v.at(i)代替v[i],故意越界看看会抛出什么异常
下一章:string,处理文字也是容器思路 🐸