🔷 第八章 结构体与共用体
📄 一句话概括

枚举值可以比较(== != < > <= >= 全都能用,因为底层是整数)、可以做算术(但结果变成 int,要存回枚举变量得强转)、可以当数组下标、最适合配 switch。教材实例06就是"枚举值的比较运算"。一句总结:枚举就像"起了名字的整数",所以整数能干的它基本都能干;但"取名字"的意义在于可读性和类型安全,别为了省事又退回写数字。

📝 本节和前面几节的关系

8.6.2 讲了"能赋值、能比较"的基本用法,本节把运算部分补齐。这里会反复回到两个老家:3.1.2 的关系运算符(比较)和 4.4 的 switch(多分支)——枚举 + switch 是 C++ 里可读性最高的一对搭档,也是本章最实用的一个组合。

一、比较运算:最常用,也最安全

enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN };

Weekday a = MON, b = FRI;

if (a == b)  cout << "同一天" << endl;
if (a != b)  cout << "不是同一天" << endl;
if (a <  b)  cout << "a 的编号更小" << endl;      // 靠底层的 0 和 4 比较

六个关系运算符全都能用:== != < > <= >=。

为什么能比大小? 因为枚举值本质是整数(8.6)。MON 是 0,FRI 是 4,所以 MON < FRI 成立。

⚠️ 但要注意语义:MON < FRI 这个比较"合法但未必有意义"——星期并不是"谁大谁优先"。比大小适合用在这种枚举上:enum Level { LOW, MID, HIGH };、enum Priority { P3, P2, P1 };——这些"等级"类枚举,比大小才有意义。

二、算术运算:结果会变成 int

Weekday today = WED;                // 2

today + 1                           // 3 —— 类型是 int,不是 Weekday
today++                             // ⚠️ 合法,但……

规则:枚举参与算术后,结果类型是 int。所以要存回枚举变量,必须强转:

Weekday tomorrow = (Weekday)(today + 1);        // 2 → 3,合法

这条正是"枚举的安全带":编译器不会让你悄悄把 int 存回枚举变量(8.6.2)。

⚠️ 更要注意边界:

Weekday last = SUN;                             // 6
Weekday bad  = (Weekday)(last + 1);             // 7 —— 越出枚举范围!

7 不在 Weekday 的定义里。编译器不报错(你强转了),但这个值是"未定义"的——拿它去比较、当数组下标都会出问题(和 7.1.2 的数组越界一样阴)。

安全做法:转之前先判断范围:

if (today < SUN) tomorrow = (Weekday)(today + 1);
else             tomorrow = MON;                // 绕回周一

三、遍历枚举:用 int 循环

for (int i = MON; i <= SUN; i++) {
    cout << i << " ";
}
// 输出:0 1 2 3 4 5 6

要点: - 循环变量用 int(不是 Weekday,因为 i++ 的结果是 int); - i <= SUN 里的 SUN 会被当成 6; - 需要枚举类型的值时就强转:Weekday d = (Weekday)i;。

这个技巧只在"编号连续"时好用(默认编号、或只给第一个赋值的情况)。如果编号跳跃(= 1, 2, 4, 8),就不能这样遍历了——只能把值一个个列出来。

四、和 switch 配合:本章最漂亮的组合

enum OrderStatus { UNPAID, PAID, SHIPPED, DONE };

void show(OrderStatus st) {
    switch (st) {
        case UNPAID:  cout << "待付款" << endl; break;
        case PAID:    cout << "已付款" << endl; break;
        case SHIPPED: cout << "已发货" << endl; break;
        case DONE:    cout << "已完成" << endl; break;
        default:      cout << "未知状态" << endl; break;
    }
}

为什么这是最佳搭配?

好处 说明
读得懂 case SHIPPED: 比 case 2: 清楚一百倍
不怕改编号 哪天把 SHIPPED 的编号改了,switch 一个字不用动
不容易漏 看着枚举列表,就知道该写几个 case
default 兜底 万一传进来一个强转出来的野值,也能接住

注意:switch 的 case 里只能写常量(4.4 的老规矩),而枚举值就是常量,正好合适。

五、动手 Demo(实例06 枚举值的比较运算)

教材实例06就是"枚举值的比较运算"。这里做成一个等级评定 + 优先级比较 + 状态机的完整小工具:

#include <iostream>
using namespace std;

enum Level { LOW = 1, MID, HIGH };                  // 等级:1~3

enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN }; // 星期:0~6

const char *levelName(Level l) {
    switch (l) {
        case LOW:  return "低";
        case MID:  return "中";
        case HIGH: return "高";
    }
    return "未知";
}

const char *weekName(Weekday w) {
    const char *n[7] = {"周一","周二","周三","周四","周五","周六","周日"};
    return n[w];
}

int main() {
    // === ① 比较运算 ===
    Level a = LOW, b = HIGH;
    cout << "=== 比较 ===" << endl;
    cout << "a = " << levelName(a) << "(" << a << "), b = " << levelName(b) << "(" << b << ")" << endl;
    cout << "a == b ? " << (a == b ? "是" : "否") << endl;      // 4.3 的三目运算符
    cout << "a <  b ? " << (a <  b ? "是" : "否") << endl;
    cout << "a >= b ? " << (a >= b ? "是" : "否") << endl;

    // === ② 算术运算(结果转 int) ===
    cout << "=== 算术 ===" << endl;
    Level c = MID;                                              // 2
    cout << "c = " << c << ",c + 1 = " << (c + 1) << "(类型是 int)" << endl;
    Level up = (Level)(c + 1);                                  // 强转回枚举
    cout << "升级后:" << levelName(up) << endl;

    // === ③ 越界的危险 ===
    cout << "=== 边界 ===" << endl;
    Level top = HIGH;
    cout << "已到最高级(" << top << "),再 +1 会变成 " << (top + 1)
         << " —— 这已经超出枚举范围了(危险)" << endl;

    // === ④ 遍历星期 ===
    cout << "=== 遍历 ===" << endl;
    for (int i = MON; i <= SUN; i++)
        cout << weekName((Weekday)i) << " ";
    cout << endl;

    // === ⑤ 比较决定休息日 ===
    cout << "=== 休息日判断 ===" << endl;
    Weekday today = SAT;
    if (today >= SAT) cout << weekName(today) << " 休息" << endl;   // 靠编号比大小
    else              cout << weekName(today) << " 上班" << endl;
    return 0;
}

看到什么算成功:

=== 比较 ===
a = 低(1), b = 高(3)
a == b ? 否
a <  b ? 是
a >= b ? 否
=== 算术 ===
c = 2,c + 1 = 3(类型是 int)
升级后:高
=== 边界 ===
已到最高级(3),再 +1 会变成 4 —— 这已经超出枚举范围了(危险)
=== 遍历 ===
周一 周二 周三 周四 周五 周六 周日
=== 休息日判断 ===
周六 休息

四个看点: - 比较运算符全都能用(== < >=),因为它们最终比的是整数; - c + 1 的结果是 int,要变回 Level 必须强转; - HIGH + 1 = 4 已经越界——强转能编过,但这个值不代表任何状态,这是枚举最阴的坑; - today >= SAT 判断休息日——用编号顺序表达"等级/优先级"是枚举比大小的正当用法。

注意 ⑤ 这种"靠编号大小判断"的写法只有约定好"SAT/SUN 排在最后"才成立。更稳的写法是 if (today == SAT || today == SUN)——不依赖编号顺序,改编号也不会坏。

30 秒实验:把 enum Weekday { MON, TUE, ... } 改成 enum Weekday { SUN, MON, TUE, WED, THU, FRI, SAT };(把周日挪到最前)。⑤ 那句 today >= SAT 立刻判断错——这就是"依赖编号顺序"的代价。改成 today == SAT || today == SUN 就不会有这个问题。

六、常见误区

  1. "枚举不能比大小"——能,六个关系运算符都支持(靠底层整数)。但要问一句"这个比大小有意义吗":等级类有意义,星期类最好别依赖顺序。
  2. "today + 1 的类型还是枚举"——是 int。存回枚举变量要强转,这是编译器在保护你。
  3. "强转后一定能用"——不一定。(Weekday)(SUN + 1) 得到 7,超出枚举范围,值无意义(和数组越界同样阴险)。转之前先判断边界。
  4. "遍历枚举时循环变量也用枚举类型"——不行(i++ 的结果是 int)。用 int 做循环变量,需要时再强转成枚举。
  5. "switch 里可以写 case today:"——不行。case 只收常量,枚举值正好是常量 ✅,但变量不行 ❌。
自测三问(点开对答案) 1. `enum Level { LOW = 1, MID, HIGH };` 里,`HIGH < LOW` 的值是?为什么合法?——`false`(因为 `HIGH` = 3、`LOW` = 1)。合法是因为**枚举底层是整数**,比较实际比的是数值。 2. `Level up = MID + 1;` 能编译过吗?怎么改?——不能(右值是 `int`)。改成 `Level up = (Level)(MID + 1);`。 3. 为什么说"枚举 + `switch`"是可读性最高的组合?——`case SHIPPED:` 比 `case 2:` 清楚;枚举编号改了 `switch` 不用改;看枚举列表就知道该写几个分支。

相关笔记