Kodewick
第 14 课,共 30 课目录

pair、struct 和 enum class

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

一个商品有名字、数量、价格;一个学生有名字和分数。几样东西总是一起出现,就把它们放在一起。最简单的是 std::pair:正好两样,用 .first 和 .second 取出来,和上一课 map 里的每一对一样。要 #include <utility>。

main.cpp
1#include <iostream>2#include <string>3#include <utility>4 5int main() {6    std::pair<std::string, int> best{"小林", 95};7    std::cout << best.first << " 考了 " << best.second << " 分\n";8    best.second = 98;9    std::pair<std::string, int> next = std::make_pair("小王", 90);10    std::cout << best.second << " " << next.first << '\n';11    return 0;12}
输出
小林 考了 95 分
98 小王

std::pair<std::string, int>:第一样是字符串,第二样是 int。std::make_pair("小王", 90) 用括号里的两个值做出一个 pair,不用写尖括号里的类型;下面的函数就用它返回结果。

pair:一次返回两个值

函数只能返回一个值。要返回两个,就返回一个 pair:

main.cpp
1#include <iostream>2#include <utility>3#include <vector>4 5std::pair<int, int> low_high(const std::vector<int>& temps) {6    int low = temps[0];7    int high = temps[0];8    for (int t : temps) {9        if (t < low) {10            low = t;11        }12        if (t > high) {13            high = t;14        }15    }16    return std::make_pair(low, high);17}18 19int main() {20    std::vector<int> week{18, 23, 16, 21, 19};21    std::pair<int, int> r = low_high(week);22    std::cout << "最低 " << r.first << ",最高 " << r.second << '\n';23    return 0;24}
输出
最低 16,最高 23

两个 pair 也能比大小:先比 .first,一样再比 .second。所以一个装 pair 的 vector 可以直接 sort:

先猜一猜

排好以后,v[1] 是哪一对?

1#include <algorithm>2#include <iostream>3#include <utility>4#include <vector>5 6int main() {7    std::vector<std::pair<int, int>> v{{2, 9}, {1, 5}, {2, 3}};8    std::sort(v.begin(), v.end());9    std::cout << v[1].first << " " << v[1].second << '\n';10    return 0;11}

struct:自己的类型

pair 只有两样,名字也只叫 first 和 second,看不出是什么。三样以上,或者想起清楚的名字,就用 struct 自己定义一个类型。里面的变量叫成员,用 . 取:

main.cpp
1#include <iostream>2#include <string>3 4struct Item {5    std::string name;6    int count;7    double price;8};9 10int main() {11    Item pen{"圆珠笔", 3, 2.5};12    pen.count += 2;13    std::cout << pen.name << ":" << pen.count << " 支,";14    std::cout << "共 " << pen.count * pen.price << " 元\n";15    return 0;16}
输出
圆珠笔:5 支,共 12.5 元

struct Item { ... }; 定义了一个新类型 Item,之后就能像 int 一样用它声明变量。Item pen{"圆珠笔", 3, 2.5} 按成员的顺序给值。点单步,看 pen 显示成三个字段。

定义 struct 的右花括号后面要有分号,这是最常忘的一个:

main.cpp
1#include <iostream>2#include <string>3 4struct Item {5    std::string name;6    int count;7}8 9int main() {10    Item cup{"杯子", 4};11    std::cout << cup.name << '\n';12    return 0;13}
输出
main.cpp:7:2: error: expected ';' after struct definition
    7 | }
      |  ^
      |  ;

g++ 指着第 7 行的 } 说:expected ';' after struct definition。

vector 里放 struct

struct 和 vector 放在一起,就是一张表:每个元素是一行,每个成员是一列。传给函数也和别的大东西一样,用 const Item&:

main.cpp
1#include <iostream>2#include <string>3#include <vector>4 5struct Item {6    std::string name;7    int count;8    double price;9};10 11double value(const Item& item) {12    return item.count * item.price;13}14 15int main() {16    std::vector<Item> stock{{"圆珠笔", 5, 2.5}, {"笔记本", 3, 8.0}, {"橡皮", 10, 1.5}};17    double total = 0;18    for (const auto& item : stock) {19        std::cout << item.name << ":" << value(item) << " 元\n";20        total += value(item);21    }22    std::cout << "合计:" << total << " 元\n";23    return 0;24}
输出
圆珠笔:12.5 元
笔记本:24 元
橡皮:15 元
合计:51.5 元

花括号里每个 {...} 是一个 Item。

按成员排序

std::sort 不知道两个 Item 谁大谁小。你写一个函数告诉它:两个参数,第一个应该排在前面就返回 true。把函数名作为第三个参数交给 sort:

main.cpp
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Student {7    std::string name;8    int score;9};10 11bool higher_score(const Student& a, const Student& b) {12    return a.score > b.score;13}14 15int main() {16    std::vector<Student> cls{{"小林", 82}, {"小王", 95}, {"小张", 77}};17    std::sort(cls.begin(), cls.end(), higher_score);18    for (const auto& s : cls) {19        std::cout << s.name << " " << s.score << '\n';20    }21    return 0;22}
输出
小王 95
小林 82
小张 77

higher_score 后面没有括号:这里不是调用它,是把它交给 sort,让 sort 需要比的时候自己去调用。用 > 是分数高的在前,用 < 就是低的在前。第 22 课会学 lambda,那时可以把比较直接写在 sort 的括号里。

enum class:给选项起名字

有些值只能是几个选项之一:尺码是小、中、大,订单是待付款、已发货、已送达。用 0、1、2 表示,过几天就忘了 2 是什么。enum class 给每个选项起名字:

main.cpp
1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6    Size s = Size::Large;7    int code = s;8    std::cout << code << '\n';9    return 0;10}
输出
main.cpp: In function 'int main()':
main.cpp:7:16: error: cannot convert 'Size' to 'int' in initialization
    7 |     int code = s;
      |                ^
      |                |
      |                Size

enum class Size 定义了一个类型,它的值只能是 Size::Small、Size::Medium、Size::Large。它们不是 int,所以 int code = s; 编译不过:cannot convert 'Size' to 'int'。这是故意的:尺码不该被当成数随便加减。

真要它对应的数,就用 static_cast<int>:

先猜一猜

这段程序显示什么?

1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6    Size s = Size::Large;7    std::cout << static_cast<int>(s) << '\n';8    return 0;9}

switch 配 enum class

enum class 和 switch 是一对好搭档:每个 case 写一个选项,读起来就像一张说明:

main.cpp
1#include <iostream>2#include <string>3 4enum class Status { Unpaid, Shipped, Delivered };5 6std::string describe(Status s) {7    switch (s) {8        case Status::Unpaid:9            return "待付款";10        case Status::Shipped:11            return "已发货";12        case Status::Delivered:13            return "已送达";14    }15    return "不明";16}17 18int main() {19    Status order = Status::Unpaid;20    std::cout << describe(order) << '\n';21    order = Status::Shipped;22    std::cout << describe(order) << '\n';23    return 0;24}
输出
待付款
已发货

每个 case 都直接 return 了,不用 break。switch 后面那个 return "不明"; 让函数在任何情况下都有返回值。

小技巧: 用 g++ -Wall 编译时,switch 漏写了某个选项,g++ 会提醒你。以后给 Status 加一个新选项,编译器就帮你找出所有要改的地方。

练一练

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

练习 1

最贵的一件

写好 most_expensive:走一遍 items,把价格最高的那件交回去。main 已经写好了,会对两组商品各问一次。

要显示
最贵:耳机 199
最贵:锅 268
exercise1.cpp
1#include <iostream>2#include <string>3#include <vector>4 5struct Item {6    std::string name;7    int price;8};9 10Item most_expensive(const std::vector<Item>& items) {11    Item best = items[0];12 13    return best;14}15 16int main() {17    std::vector<Item> desk{{"鼠标", 89}, {"耳机", 199}, {"键盘", 159}};18    std::vector<Item> kitchen{{"水壶", 129}, {"刀", 99}, {"锅", 268}};19    Item a = most_expensive(desk);20    std::cout << "最贵:" << a.name << " " << a.price << '\n';21    Item b = most_expensive(kitchen);22    std::cout << "最贵:" << b.name << " " << b.price << '\n';23    return 0;24}
输出
点「运行」或按 Ctrl+Enter
练习 2

按身高排队

写一个比较函数,把 kids 按身高从矮到高排好,再一行一个显示名字和身高。

要显示
豆豆 121
乐乐 128
果果 134
exercise2.cpp
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Kid {7    std::string name;8    int height;9};10 11int main() {12    std::vector<Kid> kids{{"乐乐", 128}, {"豆豆", 121}, {"果果", 134}};13 14    return 0;15}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:尺码不是数

这段程序编译不过。改好它,让它显示 尺码编号:1。

要显示
尺码编号:1
exercise3.cpp
1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6    Size s = Size::Medium;7    int code = s;8    std::cout << "尺码编号:" << code << '\n';9    return 0;10}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • std::pair<A, B> 装两样,用 .first、.second 取;std::make_pair 自己推出类型;函数可以返回 pair。
  • pair 先比 .first 再比 .second,所以能直接排序。
  • struct 定义自己的类型,成员用 . 取;右花括号后面要有分号。
  • std::vector<Item> 是一张表;传给函数用 const Item&。
  • 按成员排序:写一个返回 bool 的比较函数,交给 std::sort 当第三个参数。
  • enum class 给选项起名字(Size::Large),不能直接当 int,要 static_cast<int>;和 switch 一起用最清楚。