Skip to content

LV050-枚举类型

一、枚举类型简介

1. 什么是枚举?

在实际问题中,有些数据的取值往往是有限的,只能是非常少量的整数,并且最好为每个值都取一个名字,以方便在后续代码中使用,比如一个星期只有七天,一年只有十二个月,等。

针对上边那些特殊变量, C 语言为我们提供了枚举类型,在枚举的定义中,会将这些变量的值一一列举出来,并且枚举类型的变量的值就只限于列举出来的值的范围。

2. 怎么定义?

枚举类型的定义需要用到关键字 enum ,一般定义格式如下:

c
enum typeName
{ 
    valueName1, 
    valueName2, 
    valueName3, 
    ... 
    valueNameN, 
};

typeName 为枚举类型的名称, valueNameN 为枚举类型的成员。

(1)枚举类型中任意两个 枚举成员不能具有相同的名称。而且枚举列表中的 valueName1... 这些标识符的 作用范围是全局的(严格来说是 main() 函数内部),不能再定义与它们名字相同的变量

(2)枚举类型中间的枚举成员之间用逗号( , )隔开,定义结束时,分号( ; )必不可少。

(3)在枚举类型中,枚举成员的类型只能是整型,并且声明的第一个枚举成员默认值是 0 ,往后逐个加 1 (递增)。这样增加后的值必须是整型可表示的值得范围,否则会报错。

(4)我们也可以在定义枚举类型时,为枚举成员显示赋值,允许多个枚举成员具有相同的值。没有显示赋值的枚举成员的值总是前一个枚举成员的值加 1 。例如,

c
enum Weeks
{
    Monday = 1,
    Tuesday = 2,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

(5)枚举与宏其实有些类似,宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。

当我们的枚举类型定义完成后,枚举成员都是常量,它们不占用数据区(常量区、全局数据区、栈区和堆区)的内存,而是 直接被编译到命令里面放到代码区,所以不能用 & 取得它们的地址。我们也无法修改枚举成员的值,除非直接修改相应枚举类型的定义。

3. 怎么使用?

枚举类型定义后就可以直接使用了,例如:

c
#include <stdio.h>
/* 定义枚举数据类型 */
enum __CMDS {
    HOST_CMD_BASIC = 50,
    HOST_CMD_1,
    HOST_CMD_2,
    
    USER_CMD_BASIC = 100,
    USER_CMD_1,
    USER_CMD_2,
    
    INVALID_CMD = 200,
};                   /* ; 不可缺少 */

int main(int argc, char *argv[])
{ 
    printf("HOST_CMD_BASIC=%d, HOST_CMD_1=%d, HOST_CMD_2=%d\n", HOST_CMD_BASIC, HOST_CMD_1, HOST_CMD_2);
    printf("USER_CMD_BASIC=%d, USER_CMD_1=%d, USER_CMD_2=%d\n", USER_CMD_BASIC, USER_CMD_1, USER_CMD_2);
    printf("INVALID_CMD=%d\n", INVALID_CMD);
    return 0;
}

将会得到下面的输出:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
HOST_CMD_BASIC=50, HOST_CMD_1=51, HOST_CMD_2=52
USER_CMD_BASIC=100, USER_CMD_1=101, USER_CMD_2=102
INVALID_CMD=200

4. 枚举常量作用域

枚举类型中的枚举常量也是一个有效的标识符,可以拿出来单独使用,这一点和普通常量非常类似。所以说,枚举类型仅仅是将不同名字的常量松散地组织到了一起。

枚举常量的作用域和其它自定义标识符(比如变量)的作用域类似:

  • 在所有函数外部定义枚举类型时,其中的枚举常量具有全局作用域,可以在整个程序中的任何位置访问。这和全局变量是一样的。
  • 在由 { } 包围的代码块(比如函数、if、for、while 范围就无效了。这个局部变量是一样的。
c
#include <stdio.h>

// 全局枚举定义
enum Colors {RED, GREEN, BLUE};

int main() {
    printf("RED = %d\n", RED);     // 可以访问全局枚举常量
    
    enum Direction {NORTH, SOUTH, EAST, WEST};
    printf("NORTH = %d\n", NORTH); // 可以访问局部枚举常量
    
    {
        enum Status {OK = 200, ERROR = 500};
        printf("OK = %d\n", OK);   // 可以访问块作用域内的枚举常量
    }
    // printf("OK = %d\n", OK);    // 错误:OK 超出了作用域范围
    
    return 0;
}

将会得到下面的输出:

shell
RED = 0
NORTH = 0
OK = 200

5. 为什么要用枚举

  • 增强了代码的可读性,通过使用有意义的名称代替没有名称的数字,使代码更易理解。
  • 提高了类型安全性,编译器可以检查枚举变量是否被赋予了有效的值。

但枚举类型也有一些限制。例如,C 语言不允许为枚举常量指定浮点数值,也不能在运行时动态地向枚举类型添加新的常量。此外,不同的编译器可能会对枚举类型使用不同的底层整数类型,这可能会影响程序的可移植性。

这里举一个例子,比如枚举类型非常适合用在 switch 语句中,因为它们代表了一组离散的值,这样可以提高代码的可读性和可维护性,通过使用有意义的名称替代乱七八糟的数字,可以使代码更加清晰和易于理解:

c
enum Week day = Wednesday;
switch(day) {
    case Monday:
        printf("It's Monday, start of the work week.\n");
        break;
    case Wednesday:
        printf("It's Wednesday, halfway through!\n");
        break;
    case Friday:
        printf("It's Friday, weekend is coming!\n");
        break;
    default:
        printf("It's another day of the week.\n");
}

二、枚举变量

1. 怎么定义?

与结构体一样,枚举也是一种构造的数据类型,所以可以像结构体一样定义枚举变量。枚举变量定义一般格式如下:

1.1 方式一

  • 先定义枚举数据类型,再声明变量
c
/* 定义枚举数据类型 */
enum Weeks
{
    Monday = 1,
    Tuesday = 2,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

/* 定义枚举类型变量 */
enum Weeks w1, w2;

定义了两个枚举变量 w1 和 w2 。需要注意关键字 Weeks 不可以省略,若没有这个关键字,则系统不认为 Weeks 是枚举类型。

1.2 方式二

  • 定义枚举类型的同时声明枚举变量
c
/* 定义枚举类型的同时声明枚举变量 */
enum Weeks
{
    Monday = "1",
    Tuesday = "2",
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}w1, w2;

与方式一类似,定义了两个枚举变量 w1 和 w2 。如果只需要 w1 、 w2 两个变量,后面不需要再使用枚举类型名称定义其他变量,那么在定义时也可以不给出枚举类型名称

c
/* 定义枚举类型的同时声明枚举变量 */
enum
{
    Monday = "1",
    Tuesday = "2",
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}w1, w2;

2. 枚举变量的使用

2.1 引用枚举成员

c
枚举类型名1
枚举类型名2
...
枚举类型名N

2.2 枚举变量赋值

定义枚举类型以后,我们可以声明枚举类型的变量,并为其赋值了。枚举变量可以被赋予在枚举类型中定义的任何常量值,其语法格式如下:

c
enum 枚举类型名 枚举变量名 = 枚举常量名;

枚举类型变量赋值方式和结构体类似,接下来通过实例说明。需要注意的是,虽然枚举类型的本质是整型,但直接将整数赋值给枚举变量是不安全的做法,因为这可能导致变量取值超出枚举定义的范围。如果确实需要这样做,应该使用强制类型转换:

c
enum Week today = (enum Week)3; // 等价于 Wednesday
2.2.1 赋值方式一
  • 先定义枚举类型变量,再赋值
c
#include <stdio.h>
/* 定义枚举数据类型 */
enum Weeks
{
    Monday = 1,
    Tuesday = 2,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};                   /* ; 不可缺少 */

int main(int argc, char *argv[])
{ 
    enum Weeks w1, w2, w3;
    w1 = Monday;
    w2 = Sunday;
    w3 = 9;
    printf("%d %d %d %d %d %d %d\n", Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
    printf("%d %d %d \n", w1, w2, w3);
    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
1 2 3 4 5 6 7
1 7 9

【说明】我们可以把定义枚举类类型时的枚举成员赋值给枚举变量,也可以直接把整数值赋给枚举变量。枚举变量其实也就是一种 整型变量

2.2.2 赋值方式二
  • 定义枚举变量的同时进行赋值
c
#include <stdio.h>
/* 定义枚举数据类型 */
enum Weeks
{
    Monday = 1,
    Tuesday = 2,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};                   /* ; 不可缺少 */

int main(int argc, char *argv[])
{ 
    enum Weeks w1 = Monday, w2 = Friday, w3 = 10;
    
    printf("%d %d %d %d %d %d %d\n", Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
    printf("%d %d %d \n", w1, w2, w3);
    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
1 2 3 4 5 6 7
1 5 10
2.2.3 赋值方式三
  • 在定义枚举类型的同时声明变量:
c
enum Color {Red, Green, Blue} my_color1;
enum Color {Red, Green, Blue} my_color2 = Green;
2.2.4 互相赋值

枚举变量之间也可以相互赋值,但前提是这两个变量必须属于同一个枚举类型,例如:

c
enum Color my_color1;
enum Color my_color2 = Green;
my_color1 = my_color2;

三、枚举类型与整数类型

枚举类型可以与整数类型进行比较和赋值。由于枚举常量本质上是整数,我们可以将枚举变量与整数进行比较,也可以将整数赋值给枚举变量(尽管这种做法可能会导致类型安全问题),或者反过来,将枚举变量赋值给整数类型:

c
enum Week day = Tuesday;
if (day < Thursday) {    
    printf("It's early in the week.\n");
}
int num = Friday;  // num 的值为 4day = 6;  // day 的值为 Saturday