vector:一排格子
一个 int 变量只装一个数。要存一个班的分数、一张购物清单,就用 std::vector:一排连在一起的格子,格子的个数可以变。用它要 #include <vector>。
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}
a(3, 7) 是三个 7,有 3 个元素;b{3, 7} 就是 3 和 7 这两个元素,有 2 个。
push_back:往后面加
push_back 把一个新元素加在最后,vector 自己会多出一个格子。点单步,看格子一个一个填上:
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:
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] 会怎样?
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 异常;没人处理这个异常,程序就到此结束:
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 |
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}
b = a 复制了全部元素,b 是另一排格子。改 b[0] 不影响 a。
传参数也是这样:直接写 std::vector<int> 会复制整个 vector。和第 10 课的字符串一样,只读就传 const std::vector<int>&,要改就传 std::vector<int>&:
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 元
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
一周的步数
✓steps 里是一周七天的步数。用循环把它们加起来,显示 总步数:51300。
总步数:51300
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}
带编号的购物清单
✓从空的清单开始,用 push_back 依次加上 米、油、盐,再用循环显示带编号的清单(编号从 1 开始)。
1. 米 2. 油 3. 盐
1#include <iostream>2#include <string>3#include <vector>4 5int main() {6 std::vector<std::string> list;7 8 return 0;9}
修好它:差一格的最后一个
✓这段程序想显示最后一个价格,却越界了。改好它,让它显示 最后一个:45。
最后一个:45
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}
这一课学会了
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>&(要改)。