Skip to content

📦11. Vector 向量

数组虽然使用方便,但其长度固定的局限性十分突出——一旦定义,无法在运行时改变大小。vector 是 C++ 标准库提供的动态数组容器,可自动管理存储空间。

0. 逻辑一致,但是类型不同

假设要写一个函数,打印一个数的值:

cpp
void printValue(float x) {
    cout << "值是:" << x << endl;
}

这个函数只能接收 float。如果还想打印 intdoublestring应该怎么办呢?

在C++中,我们可以把每种类型都写一个版本:

cpp
void printValue(int x) {
    cout << "值是:" << x << endl;
}

void printValue(double x) {
    cout << "值是:" << x << endl;
}

void printValue(string x) {
    cout << "值是:" << x << endl;
}

这样调用方写起来很简洁:printValue(3)printValue(3.14)printValue("hello")

这种"同一个函数名,多个不同参数类型版本"的写法叫函数重载

在C语言中,没有重载,所以这种情况只能写3个不同的函数名字。

0.2 冗繁的重载

上面的写法有一个问题:代码重复

如果程序里需要支持 10 种、20 种类型,就得写 10 份、20 份几乎一模一样的函数——函数体完全一样,只是参数类型不同。

有没有办法把"类型"也当作参数传进去?

0.3 把类型也放到参数里

C++提供了一个更方便的写法——使用关键字 template

cpp
template <typename T>
void printValue(T x) {
    cout << "值是:" << x << endl;
}

调用的时候,编译器会自动根据参数推断 T 是什么,然后生成对应版本的代码:

cpp
printValue(3);          // T 是 int,生成 int 版本
printValue(3.14);       // T 是 double,生成 double 版本
printValue("hello");    // T 是 string,生成 string 版本

只需要写一份代码,编译器帮忙生成所有类型的版本。

这里的 T 叫作类型参数,它充当具体类型的"占位符"。编译器为每种类型生成一份代码的过程叫模板实例化

这种用 template<typename T> 定义函数的方式叫函数模板

例子:求两个数的最大值:

cpp
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

cout << max(3, 5);           // int → 5
cout << max(3.14, 2.71);     // double → 3.14
cout << max('A', 'B');       // char → 'B'

0.4 两个参数类型不同呢?

max(3, 4.5) 传了一个 int 和一个 doubleT 应该是什么?

编译器无法确定。这时需要声明两个类型参数:

cpp
template <typename T, typename U>
auto max(T a, U b) {
    return (a > b) ? a : b;
}

cout << max(3, 4.5);        // T=int, U=double → 4.5
cout << max(2.71, 3);       // T=double, U=int → 3.0

类型参数可以有多个,用逗号隔开:

cpp
template <typename T, typename U, typename V>

auto 让编译器根据 return 自动推导返回类型(C++14 起可用)。

0.5 类也有同样的问题

函数有"写一个,适配任意类型"的需求,类也有。

vector 就是一个例子——它需要能存放 intdoublestring 等任意类型的元素。

和函数模板一样,vector 内部也用了 template<typename T>

vector<int>    →  元素类型是 int 的 vector
vector<double> →  元素类型是 double 的 vector

尖括号 <> 里的内容就是告诉 vector"你的元素是什么类型"。

实际上上一课学的 string 也是模板类(basic_string<char> 的别名),只是 <> 在别名定义里被隐藏了。

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();    // 4

4.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();    // 0

4.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,你会发现它们有很多相似之处:

操作stringvector<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 章节,你还会学到更多的容器(如 listmap),操作方式都非常相似!

9. 总结

  • vector 是 C++ 的动态数组,长度可以随数据量自动变化
  • push_back() 在尾部添加,用 [].at() 访问
  • .size() 获取当前元素个数
  • 可以用下标或范围 for 循环遍历
  • 优先用 vector,而不是传统数组——除非你有非常特殊的理由

10. 练习题

  1. 编写一个函数模板 myMin,能返回两个参数中较小的那个。
  2. 编写一个函数模板 sum,接受两个可能不同类型的参数,返回它们的和。
  3. 编写一个类模板 Box,能存储任意类型的单个值,并提供 getValue() 方法返回该值。
  4. 以下代码能编译通过吗?为什么?
cpp
template <typename T>
T add(T a, T b) 
{ 
    return a + b; 
}

cout << add(3, 4.5);