Appearance
📦11. Vector 向量
数组虽然使用方便,但其长度固定的局限性十分突出——一旦定义,无法在运行时改变大小。
vector是 C++ 标准库提供的动态数组容器,可自动管理存储空间。
0. 模板基础
在学习 vector 之前,须先理解模板的概念,以便正确认识 vector<int> 中 <int> 的含义。
0.1 问题:类型重复带来的代码冗余
假设已编写一个 IntArray 类,用于存储整数:
cpp
class IntArray {
int data[100]; // 最多存储 100 个整数
int size = 0;
public:
void add(int val) { data[size++] = val; }
};若后续需要存储 double 或 string 类型,则需编写几乎相同的 DoubleArray、StringArray 等类。
这种重复显然是低效的——代码逻辑完全一致,仅类型不同。
0.2 模板:类型的参数化
模板(template) 是一种类型参数化机制——在编写代码时暂不指定具体类型,留待使用时再由编译器确定:
cpp
template <typename T>
class MyArray {
T data[100]; // T 为类型参数,实例化时替换为具体类型
int size = 0;
public:
void add(T val) { data[size++] = val; }
};这里的 T 是一个类型参数(type parameter),充当具体类型的占位符。使用时:
cpp
MyArray<int> arr1; // T = int,编译器生成 int 版本
MyArray<double> arr2; // T = double,编译器生成 double 版本
MyArray<string> arr3; // T = string编译器遇到
MyArray<int>时,将模板中所有T替换为int,生成一份专门处理int类型的代码——此过程称为模板实例化。
0.3 直观类比
模板(类模板) 具体的类
┌──────────────┐
MyArray<int> │ int 版本 │
MyArray<double> = │ double 版本 │
MyArray<string> │ string 版本 │
└──────────────┘一份模板可以生成多个不同具体类型的类。
0.4 函数模板
不仅类可以模板化,函数同样支持模板:
cpp
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
cout << max(3, 5); // T = int → 返回 5
cout << max(3.14, 2.71); // T = double → 返回 3.14
cout << max('A', 'B'); // T = char → 返回 'B'同一份 max 函数模板可适用于整数、浮点数、字符等多种类型,无需为每种类型单独编写。
0.5 模板、类与对象的关系
将三个概念串联起来:
模板(template) → 类(class) → 对象(object)
│ │ │
│ 实例化 │ 创建 │ 使用
▼ ▼ ▼
vector<int> v v.push_back(10)| 概念 | 说明 |
|---|---|
模板 template<typename T> | 类型参数化的类定义,T 为类型参数 |
具体的类 vector<int> | 将 T 替换为具体类型后生成的类 |
对象 v | 根据类创建的实际变量 |
类是面向对象编程的基础,模板则进一步提升了类的通用性——使一个类能够适配任意数据类型,避免重复编码。实际上
string也是一个模板类(basic_string<char>的别名),只是<char>在别名定义中被隐藏了。
0.6 vector<int> 的语义解读
vector<int> → 元素类型为 int 的 vector
vector<double> → 元素类型为 double 的 vector尖括号 <> 内指定容器所要存储的元素类型。
理解模板的基本概念后,接下来正式学习 vector 这一最常用的模板类。
1. 数组的局限性
cpp
int scores[5]; // 固定存储 5 个整数
// 若需存储第 6 个数据,数组容量已不足实际编程中,数据量往往在程序运行时才能确定。例如:
- 需要读入 N 个数,但 N 的值由用户输入决定
- 从文件中读取学生成绩,文件中的学生数量未知
- 数据需要动态增长,无法预先估算最大容量
传统数组难以应对这类场景——要么分配过多造成空间浪费,要么分配不足导致数据溢出。
vector 正是为解决这一问题而设计。
2. vector 概述
vector 是 C++ 标准库提供的动态数组容器(亦称向量),定义于 <vector> 头文件中。
cpp
#include <vector>其主要特性如下:
- 动态扩容:元素数量增长时自动分配更多内存
- 长度查询:通过
.size()成员函数获取当前元素个数 - 下标访问:支持
v[0]、v[1]等下标操作,语法与数组一致 - 丰富接口:支持尾部添加、尾部删除、插入、清空等操作
vector本身也是一个类(基于模板实现),因此自带了各类成员函数。
3. 定义与初始化
cpp
#include <vector>
vector<int> v1; // 空的 vector,元素类型为 int
vector<int> v2(5); // 包含 5 个元素,均初始化为 0
vector<int> v3(5, 3); // 包含 5 个元素,均初始化为 3 → [3,3,3,3,3]
vector<int> v4 = {1, 2, 3}; // 列表初始化 → [1,2,3](C++11 起支持)语法格式为 vector<类型>,类型参数置于尖括号 <> 中。
尖括号语法源自模板机制——
vector是一个可适配任意元素类型的容器。vector<double>表示存储double类型的动态数组,vector<string>表示存储string类型的动态数组。
4. 基本操作
4.1 尾部添加 —— push_back()
cpp
vector<int> v;
v.push_back(10); // v = [10]
v.push_back(20); // v = [10, 20]
v.push_back(30); // v = [10, 20, 30]push_back() 在 vector 尾部追加一个新元素。vector 内部自动管理内存分配,当前容量不足时自动扩容。
4.2 元素访问 —— [] 与 .at()
cpp
vector<int> v = {10, 20, 30};
cout << v[0]; // 10,下标操作与数组一致
cout << v[1]; // 20
cout << v[2]; // 30
v[0] = 99; // 可通过下标修改元素.at() 与 [] 功能相似,但额外执行越界检查:
cpp
cout << v[100]; // 越界访问,行为未定义(可能读到垃圾数据或导致崩溃)
cout << v.at(100); // 越界访问,抛出异常(安全性更高)4.3 获取长度 —— .size()
cpp
vector<int> v = {10, 20, 30};
cout << v.size(); // 3
v.push_back(40);
cout << v.size(); // 44.4 删除尾部元素 —— .pop_back()
cpp
vector<int> v = {10, 20, 30};
v.pop_back(); // 删除最后一个元素 → v = [10, 20]
v.pop_back(); // → v = [10]4.5 判空和清空
cpp
vector<int> v = {10, 20, 30};
cout << v.empty(); // false(不为空)
v.clear(); // 清空所有元素
cout << v.empty(); // true
cout << v.size(); // 04.6 遍历 vector
方式一:下标遍历
cpp
vector<int> v = {10, 20, 30, 40, 50};
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
// 输出:10 20 30 40 50方式二:基于范围的 for 循环(C++11)
cpp
for (int x : v) {
cout << x << " ";
}
// 输出:10 20 30 40 50基于范围的 for 循环语法更为简洁,无需处理下标索引,直接遍历每个元素。
5. 常用操作速查表
| 操作 | 写法 | 说明 |
|---|---|---|
| 定义 | vector<int> v; | 空 vector |
| 尾部添加 | v.push_back(x); | 在末尾加一个元素 |
| 尾部删除 | v.pop_back(); | 删除最后一个元素 |
| 访问元素 | v[i] 或 v.at(i) | 按下标访问 |
| 长度 | v.size() | 当前元素个数 |
| 判空 | v.empty() | 空返回 true |
| 清空 | v.clear() | 删除所有元素 |
| 排序 | sort(v.begin(), v.end()); | 需要<algorithm> 头文件 |
TIP
sort(v.begin(), v.end()) 可以对 vector 排序,默认从小到大。需要 #include <algorithm>。
6. vector 与数组对比
| 对比项 | 传统数组 int a[N] | vector |
|---|---|---|
| 长度 | 固定,编译时确定 | 动态,运行时可变 |
| 长度获取 | 需通过 sizeof 计算 | .size() 直接获取 |
| 越界检测 | 无越界检查(危险) | .at() 执行越界检查 |
| 参数传递 | 退化为指针,长度信息丢失 | 传递 vector 本身,长度信息保留 |
| 新增元素 | 无法动态增加 | push_back() 随时添加 |
| 性能 | 略优(无额外开销) | 基本无差异(现代编译器的优化) |
除嵌入式系统等内存极度受限的场景外,应优先使用 vector 而非传统数组。
7. 例题:统计成绩
题目: 输入 N 个学生的成绩,输出最高分、最低分和平均分。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cout << "请输入学生人数:";
cin >> n;
vector<int> scores; // 用 vector 存成绩
cout << "请输入 " << n << " 个成绩:";
for (int i = 0; i < n; i++) {
int s;
cin >> s;
scores.push_back(s); // 逐个添加到 vector
}
// 计算最高分、最低分、总分
int max = scores[0];
int min = scores[0];
int sum = 0;
for (int i = 0; i < scores.size(); i++) {
if (scores[i] > max) max = scores[i];
if (scores[i] < min) min = scores[i];
sum += scores[i];
}
double avg = (double)sum / scores.size();
cout << "最高分:" << max << endl;
cout << "最低分:" << min << endl;
cout << "平均分:" << avg << endl;
return 0;
}如果用传统数组,你得先知道 N 才能定义数组大小——而 vector 可以在不知道 N 的情况下先定义好,边读边加。
8. 和 string 的联系
学完 string 又学 vector,你会发现它们有很多相似之处:
| 操作 | string | vector<int> |
|---|---|---|
| 长度 | s.length() / s.size() | v.size() |
| 下标访问 | s[i] | v[i] |
| 尾部添加 | s += 'a' 或 s.push_back('a') | v.push_back(x) |
| 判空 | s.empty() | v.empty() |
| 清空 | s.clear() | v.clear() |
这是 C++ 标准库的设计理念之一:不同类型的容器,操作接口尽量保持一致,这样学起来事半功倍。
后面在 L2 的 STL 章节,你还会学到更多的容器(如
list、map),操作方式都非常相似!
9. 总结
vector是 C++ 的动态数组,长度可以随数据量自动变化- 用
push_back()在尾部添加,用[]或.at()访问 .size()获取当前元素个数- 可以用下标或范围 for 循环遍历
- 优先用 vector,而不是传统数组——除非你有非常特殊的理由