Kodewick
第 11 课,共 30 课目录

vector:一排格子

第 11 课,共 30 课 · 第 3 部分 · 容器 · 约 15 分钟

一个 int 变量只装一个数。要存一个班的分数、一张购物清单,就用 std::vector:一排连在一起的格子,格子的个数可以变。用它要 #include <vector>。

main.cpp
1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> scores{90, 85, 77};6    std::cout << "第一个:" << scores[0] << '\n';7    std::cout << "一共:" << scores.size() << " 个\n";8    return 0;9}
输出
第一个:90
一共:3 个

std::vector<int> 里尖括号中的 int 说的是:每个格子里装一个 int。格子里的值叫元素,scores[0] 是第一个元素,方括号里的数叫下标,从 0 开始。size() 告诉你现在有几个元素。点单步,看 scores 显示成一排三个格子。

建一个 vector

常见的几种写法:

写法得到
std::vector<int> v;空的,一个元素也没有
std::vector<int> v{4, 8, 15};三个元素:4、8、15
std::vector<int> v(5, 0);五个元素,都是 0
std::vector<std::string> v;空的,以后装字符串

注意花括号和圆括号不一样:花括号里列的是元素本身,圆括号里是「几个、每个是多少」。

先猜一猜

这段程序显示什么?

1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> a(3, 7);6    std::vector<int> b{3, 7};7    std::cout << a.size() << " " << b.size() << '\n';8    return 0;9}

push_back:往后面加

push_back 把一个新元素加在最后,vector 自己会多出一个格子。点单步,看格子一个一个填上:

main.cpp
1#include <iostream>2#include <string>3#include <vector>4 5int main() {6    std::vector<std::string> list;7    std::cout << "开始:" << list.size() << " 样\n";8    list.push_back("牛奶");9    list.push_back("鸡蛋");10    list.push_back("面包");11    std::cout << "现在:" << list.size() << " 样\n";12    std::cout << "最后一样:" << list[2] << '\n';13    return 0;14}
输出
开始:0 样
现在:3 样
最后一样:面包

不用事先说好要几个格子:先建一个空的,有一样加一样,这是 vector 最常见的用法。

下标和循环

v[i] 能读,也能改。要把每个元素走一遍,就让下标从 0 走到 size() - 1:

main.cpp
1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> scores{90, 85, 77, 68};6    scores[1] = 88;7    int total = 0;8    for (std::size_t i = 0; i < scores.size(); i++) {9        std::cout << i << ":" << scores[i] << '\n';10        total += scores[i];11    }12    std::cout << "平均:" << static_cast<double>(total) / scores.size() << '\n';13    return 0;14}
输出
0:90
1:88
2:77
3:68
平均:80.75

下标变量 i 的类型是 std::size_t:一种不会是负数的整数类型,专门用来表示大小和下标,size() 给的也是它。

注意: size() 不会是负数,所以空的 vector 上 size() - 1 不是 -1,而是一个非常大的数。写 i < v.size(),不要写 i <= v.size() - 1。

越界

下面的 vector 只有三个元素,下标只能是 0、1、2。读 prices[5] 会怎样?

main.cpp
1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> prices{12, 30, 8};6    std::cout << "第一个:" << prices[0] << '\n';7    std::cout << "第六个:" << prices[5] << '\n';8    return 0;9}
输出
第一个:12
第六个:
index out of range

发生了什么: 停在第 7 行:你访问了 vector[5],可它只有 3 个元素(下标 0~2)。

方括号不检查下标对不对。在真正的编译器里,这是未定义行为:可能显示一个奇怪的数,可能崩溃,也可能看起来什么事都没有,这才最危险。我们的引擎会停在出事的那一行,告诉你哪里越界了。

at(i) 和 [i] 一样取元素,但它会先检查下标。下标不对,它就抛出一个 std::out_of_range 异常;没人处理这个异常,程序就到此结束:

main.cpp
1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> prices{12, 30, 8};6    int sixth = prices.at(5);7    std::cout << "第六个:" << sixth << '\n';8    std::cout << "这一行也不会运行\n";9    return 0;10}
输出
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 5) >= this->size() (which is 3)
Aborted (core dumped)

发生了什么: at(5) 超出了 vector 的大小 3(std::out_of_range)。

程序在 at(5) 这一行就结束了,后面的都不会运行。页面上显示的 terminate called after throwing an instance of 'std::out_of_range' 和 Aborted (core dumped),和你在自己电脑上用 g++ 编出来的程序一样;中间 what(): 后面说清楚了:下标 5(__n),可是只有 3 个元素(size())。怎么接住异常、让程序继续跑,第 26 课再讲。

小技巧: 拿不准下标对不对的时候用 at():出了错马上知道,不会悄悄读到一个怪数。

第一个和最后一个

写法做什么
v.front()第一个元素,和 v[0] 一样
v.back()最后一个元素,和 v[v.size() - 1] 一样
v.pop_back()去掉最后一个元素
v.empty()一个元素都没有时是 true
main.cpp
1#include <iostream>2#include <string>3#include <vector>4 5int main() {6    std::vector<std::string> todo{"买菜", "交电费", "洗衣服"};7    std::cout << todo.front() << "," << todo.back() << '\n';8    todo.pop_back();9    std::cout << "还剩 " << todo.size() << " 件,最后是" << todo.back() << '\n';10    while (!todo.empty()) {11        todo.pop_back();12    }13    std::cout << "还剩 " << todo.size() << " 件\n";14    return 0;15}
输出
买菜,洗衣服
还剩 2 件,最后是交电费
还剩 0 件

while (!todo.empty()) 的意思是「还有就继续」。空的 vector 没有第一个也没有最后一个:在它上面用 front()、back() 或 pop_back() 都是未定义行为,用之前先看看 empty()。

复制和传给函数

把一个 vector 赋给另一个,会把所有元素复制一份。之后两个各管各的:

先猜一猜

这段程序显示什么?

1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> a{1, 2, 3};6    std::vector<int> b = a;7    b[0] = 100;8    std::cout << a[0] << " " << b[0] << '\n';9    return 0;10}

传参数也是这样:直接写 std::vector<int> 会复制整个 vector。和第 10 课的字符串一样,只读就传 const std::vector<int>&,要改就传 std::vector<int>&:

main.cpp
1#include <iostream>2#include <string>3#include <vector>4 5int total_price(const std::vector<int>& prices) {6    int total = 0;7    for (std::size_t i = 0; i < prices.size(); i++) {8        total += prices[i];9    }10    return total;11}12 13void add_item(std::vector<int>& prices, int price) {14    prices.push_back(price);15}16 17int main() {18    std::vector<int> cart{12, 30};19    add_item(cart, 8);20    std::cout << cart.size() << " 样,共 " << total_price(cart) << " 元\n";21    return 0;22}
输出
3 样,共 50 元

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

一周的步数

steps 里是一周七天的步数。用循环把它们加起来,显示 总步数:51300。

要显示
总步数:51300
exercise1.cpp
1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> steps{6200, 8100, 7500, 9000, 5400, 10300, 4800};6 7    return 0;8}
输出
点「运行」或按 Ctrl+Enter
练习 2

带编号的购物清单

从空的清单开始,用 push_back 依次加上 米、油、盐,再用循环显示带编号的清单(编号从 1 开始)。

要显示
1. 米
2. 油
3. 盐
exercise2.cpp
1#include <iostream>2#include <string>3#include <vector>4 5int main() {6    std::vector<std::string> list;7 8    return 0;9}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:差一格的最后一个

这段程序想显示最后一个价格,却越界了。改好它,让它显示 最后一个:45。

要显示
最后一个:45
exercise3.cpp
1#include <iostream>2#include <vector>3 4int main() {5    std::vector<int> prices{20, 35, 45};6    std::cout << "最后一个:" << prices[prices.size()] << '\n';7    return 0;8}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • std::vector<类型> 是一排格子,要 #include <vector>;花括号列出元素,(n, x) 是 n 个 x。
  • push_back 往后加,pop_back 去掉最后一个;size() 是元素个数,empty() 看是不是空的。
  • v[i] 读和改元素,下标从 0 到 size() - 1,循环写 i < v.size()。
  • v[i] 越界是未定义行为;v.at(i) 越界会抛出 std::out_of_range,程序结束。
  • front() 和 back() 是第一个和最后一个;空的 vector 上不能用。
  • 赋值会复制整个 vector;传给函数用 const std::vector<int>&(只读)或 std::vector<int>&(要改)。