HOWTO · C

如何在 C 语言中使用 typedef enum

本文介绍如何在 C 语言中使用 typedef enum,解释其优点并提供清晰的示例。学习如何使用 typedef enum 提高代码的可读性和可维护性,并了解如何在结构体中使用它。无论您是初学者还是希望提升技能,本指南都将帮助您轻松掌握 C 语言中的枚举。

本页内容

在 C 语言中,enum 声明具名整数常量,typedef 可以为枚举类型提供一个更短的名称。使用这些名称可以使状态值和选项值更易于阅读,然后在输入或存储的数据可能来自程序外部的任何地方显式处理这些值。枚举提高了清晰度,但它不会自动验证分配给变量的每个值。

定义枚举并赋值

没有显式值的枚举成员从 0 开始;每个后续的枚举成员比前一个值大一。例如,enum { MONDAY, TUESDAY, WEDNESDAY }; 分配 0、1 和 2。当值需要与现有的格式、协议或稳定的代码集相匹配时,您可以显式赋值:enum Status { STATUS_READY = 1, STATUS_RUNNING = 4, STATUS_DONE = 8 };。在显式值之后,下一个隐式值比该值大一。

诸如 STATUS_READY 之类的名称是整数常量。它们不是变量。枚举变量稍后可以被赋予不同的值,并且 C 通常不会将其限制为其声明中列出的枚举成员。请将来自输入、文件或其他外部来源的值视为在使用前需要检查的数据。

为枚举创建 typedef 别名

枚举标签和 typedef 别名是相关但不同的名称。使用 enum State { ... }; 时,声明使用 enum State。typedef 可以引入更短的别名 State,从而允许像 State current; 这样的声明。别名指的是同一类型;typedef 不会创建不同的类型。组合写法 typedef enum State { ... } State; 同时声明标签和别名。

此示例提示输入一个整数,检查读取是否成功,并在将该值转换为 State 并存储到 Job 字段之前对其进行验证。输入 2 以查看成功路径。程序在同一行打印提示和回显的值,然后 switch 打印 Running。如果读取失败或数字不在支持的三种状态范围内,程序会打印一条消息并在转换之前停止。

#include <stdio.h>

typedef enum State {
    STATE_READY = 1,
    STATE_RUNNING = 2,
    STATE_DONE = 3
} State;

typedef struct Job {
    State state;
} Job;

int main(void) {
    int input;
    printf("Input: ");
    if (scanf("%d", &input) != 1) {
        puts("Could not read an integer.");
        return 0;
    }
    printf("%d\n", input);

    if (input != STATE_READY && input != STATE_RUNNING && input != STATE_DONE) {
        puts("Unsupported state.");
        return 0;
    }

    Job job = { (State)input };
    switch (job.state) {
        case STATE_READY:
            puts("Ready");
            break;
        case STATE_RUNNING:
            puts("Running");
            break;
        case STATE_DONE:
            puts("Done");
            break;
        default:
            puts("Unsupported state.");
            break;
    }

    return 0;
}
Input: Could not read an integer.

在结构体中使用枚举类型

上面的 Job 结构体将其状态存储在 State 字段中。这使状态与描述作业的其他数据放在一起,同时枚举常量使赋值和 switch 分支易于阅读。如果您选择不定义 typedef 别名,也可以使用标签将该字段声明为 enum State state;。

结构体不会为该字段添加验证。如果该值来自可能提供不受支持的数字的来源,请在使用之前对其进行检查。default 分支使程序在存储的值与任何具名状态都不匹配时具有明确的响应。如果某个特定的应用程序需要更严格的保证,请在值进入程序的边界处进行验证,并使该检查靠近赋值位置。

选择枚举值并安全地使用它们

当常量是内部标签且其数值表示形式无关紧要时,请使用隐式值。当其他接口需要特定的数字时,请显式赋值,并且如果数据被持久化或交换,请保持这些赋值稳定。在任何一种情况下,具名常量都比未解释的整数文字更好地传达意图,并为维护者提供一个可以查看可用标签的地方。

这些优点并不会使枚举变量成为只读变量,也不能保证它们仅包含具名的枚举成员。变量在执行期间可能会发生变化,强制转换或外部输入可能会产生没有相应名称的值。使用带有 default 分支的 switch,或在处理该值之前根据已接受的枚举成员进行验证。这样可以防止意外数据悄悄地走上为已知状态预设的路径。

总之,为具名常量声明枚举,在能使声明更清晰时添加 typedef 别名,并在状态所属的任何地方使用结果类型,包括结构体字段和函数参数。仅当数字是需求的一部分时才选择显式数值,并使验证与枚举提供的命名便利保持分离。