← 课程目录
第 16 讲 模块五:批量处理数据

vector:会变长的数组

16 / 24
💡
本节课目标
学习 vector——一种不用提前定长、可以随时用 push_back 添加元素、用 .size() 取长度的容器, 并搞清楚它和数组相比,好在哪里、又有多相似。
开场

数组的老毛病:长度定死了

📖 理论讲解

回忆第 14 讲的遗留问题

数组声明的那一刻,长度就永远固定了:

array_fixed.cpp CPP
int scores[5];   // 5 个人的分数,多一个都装不下
// 如果这次只有 3 个人报名?如果下次来了 8 个人?
⚠️
现实里,数据量常常是运行时才知道的
“这次班里有几个人报名”这种信息,写代码的时候根本不知道答案。 数组要求“声明时就定死长度”,这种场景就很别扭。
第一段

vector:声明和初始化

📖 理论讲解

需要先 #include <vector>

vector 不是内置在语言里的,用之前要 #include <vector>(和 string 类似)。 声明语法是 std::vector<类型> 变量名,尖括号里写清楚 要装的元素类型:

vector_declare.cpp CPP
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores;   // 声明一个空的、能装 int 的 vector

    std::vector<int> prices = {10, 20, 30};   // 也可以像数组一样带初始值
    std::cout << prices[0] << std::endl;      // 下标访问,和数组一模一样
    return 0;
}
💡
下标访问和数组完全一样
prices[0] 这种写法,vector 和数组用法一致—— 下标同样从 0 开始,同样用中括号。会用数组,就已经会用 vector 一大半了。
⚠️
vector 越界,一样危险
第 14 讲讲过数组越界“不可预测”,vector 同样成立—— prices[10] 对一个只有 3 个元素的 vector 来说照样越界, 使用 [] 时 C++ 不会自动帮你检查vector 能变的是长度, 不是“越界访问会不会出事”这条规矩。
需要检查时,用 .at()
prices.at(0)prices[0] 都能读取第一个元素; 区别是 .at() 会检查下标,越界时抛出 std::out_of_range, 更容易发现问题。初学阶段可以记成:[] 简洁, .at() 更安全但多一次检查。
能变的是长度,不是元素类型
std::vector<int> 声明好之后,只能装 int—— 这一点和数组一样死板,变的只是“能装多少个”,不是“能装什么”。
第二段

push_back:随时往后面加一个

📖 理论讲解

不用提前想好装多少个

scores.push_back(90); 的意思是"在 scores 的末尾, 新增一个值为 90 的格子"。每调用一次 push_back,长度就自动 加一——这是数组做不到的事。

push_back.cpp CPP
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores;

    scores.push_back(90);   // 装进第 1 个
    scores.push_back(85);   // 再装进第 2 个,长度自动变成 2
    scores.push_back(70);   // 再装一个,长度自动变成 3

    std::cout << scores[0] << " " << scores[1] << " " << scores[2] << std::endl;
    return 0;
}
⚡ 动手实操 约 5 分钟

再多塞几个

push_back.cpp 基础上,再多调用几次 push_back, 塞进你自己想要的数字,然后打印新加的那些元素,确认它们的下标接着往后排。

第三段

.size():随时知道当前有多少个

📖 理论讲解

不用自己另外记一个长度变量

数组的长度得自己心里记住(或者另外声明一个变量存着);vector 自带 .size(),随时能问它“你现在装了几个?”,配合循环遍历特别方便:

size_loop.cpp CPP
#include <cstddef>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores = {90, 85, 70, 60, 95};

    std::cout << "一共 " << scores.size() << " 个分数:" << std::endl;
    for (std::size_t i = 0; i < scores.size(); i++) {
        std::cout << scores[i] << std::endl;
    }
    return 0;
}
遍历写法和数组几乎一模一样
唯一的区别就是循环条件从写死的数字(比如 i < 5)换成了 i < scores.size()——上节课学的求和、找最大值那套算法, 原封不动搬到 vector 上都能用。
💡
为什么下标用 std::size_t
.size() 返回的是专门表示大小和下标的无符号类型, 通常写作 std::size_t,并不是普通的 int。 让循环变量也使用 std::size_t,可以避免有符号与无符号整数比较的编译警告。
📖 理论讲解

拓展:不需要下标时,用范围 for

如果只想依次拿到每个元素,不关心它是第几个,可以用更简洁的 范围 for。每一轮,score 会自动取得 scores 中的下一个元素:

range_for.cpp CPP
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores = {90, 85, 70, 60, 95};

    for (int score : scores) {
        std::cout << score << std::endl;
    }
    return 0;
}
第四段

不知道有几个数据?边输入边装

📖 理论讲解

回忆第 8 讲:不确定次数的循环

如果连“要输入几个数据”都不知道,正好是 while 的主场: 不断读入,直到用户输入某个“结束信号”(这里用 -1)为止:

dynamic_input.cpp CPP
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores;

    std::cout << "不断输入分数,输入 -1 结束:" << std::endl;
    int x;
    while (std::cin >> x && x != -1) {
        scores.push_back(x);
    }

    std::cout << "一共录入了 " << scores.size() << " 个分数。" << std::endl;
    return 0;
}
把读取是否成功也写进循环条件
条件 std::cin >> x && x != -1 有两道关: 先确认成功读到了一个整数,再确认它不是结束信号。用户输入非数字或发送文件结束信号时, 循环也会安全停止,不会卡在失败状态里不断运行。
💡
这正是 vector 存在的意义
这种“用户想输几个就输几个”的场景,原生定长数组很不方便——声明数组时根本不知道该填多大的长度。 vectorpush_back 边输入边变长,正好解决这个问题。
实战

动手写三个 vector 程序

练习 16-1

录入并遍历

声明一个 std::vector<int>,用 push_back 塞进 5 个你喜欢的数字,用循环遍历打印每一个,并打印 .size() 的结果。

练习 16-2

不知道几个也能存

仿照 dynamic_input.cpp,让用户不断输入正整数, 直到输入 0 结束,把每次输入的数存进 vector, 最后打印“一共输入了几个数”。

练习 16-3

vector 求和

对练习 16-1 里的 vector,求出并打印所有元素的总和。

提示:把第 15 讲的累加器套路搬过来,循环条件换成 .size() 即可。

收尾

小结

📖 理论讲解

这节课你掌握了

能力用到的工具
声明一个动态容器std::vector<类型> 变量名;
往末尾添加元素.push_back(值)
取当前长度.size()
读写某个元素[下标],或用会检查范围的 .at(下标)
不关心下标地遍历范围 for

记一句话:确定装多少个、追求简单,用数组;不确定装多少个、可能要动态增加,用 vector

💡
下节课预告
模块四讲过的 string,其实骨子里也很像一个“字符的数组”—— 下节课复习巩固字符串常用操作:用 .length() 取长度、用下标访问单个字符、 遍历字符串查找特定字符。