C++枚举

你的名字 2022-08-05 00:58 341阅读 0赞

1. 介绍

第一次学习枚举类型时,觉得这个名字很诡异。但是后来发现,“枚举”真的特别传神,枚举就是可数的意思。

当你发现某个类型的值是数得过来的,那就派枚举出场吧。

2. C++11前的枚举

C++11是个大版本,一定程度上重新定义了C++,其中就包括新增的emum class。追本溯源,我们先看老枚举。

  1. enum Color { WHITE=1, BLACK=2 };

名字冲突

如果我们的代码定义了两个不同的枚举类型,而它们的枚举值取了相同的名字,那么编译器将报错。

  1. enum Color { WHITE=1, BLACK=2 };
  2. enum Species { BLACK=1, WHITE=2, YELLOW=3 };

这种错误在以下情况很容易发生

  • 多人协作,员工A还来不及知道员工B是怎么定义Color的
  • 与库冲突,如果Color是在库中定义的,程序员更有可能如此定义

于是公司为了减少此类错误,可能制定如下编程规范:
所有的枚举,值的名字都要以该枚举名为前缀

于是Species定义如下,是不是略感繁琐呢?

  1. enum Species {
  2. SPECIES_BLACK = 1,
  3. SPECIES_WHITE = 2,
  4. SPECIES_YELLOW = 3
  5. };

类型不安全

名字冲突会在编译期暴露,但是类型问题却会躲过编译器的审查,导致难以追踪的逻辑Bug,更为可怕。老式枚举很大程度上只是个整数类型。

枚举可以直接和数字比较

  1. enum Color { COLOR_WHITE=1, COLOR_BLACK=2 };
  2. if (COLOR_WHITE == 1) {
  3. // do something
  4. }

不同的枚举也可以比较

  1. enum Color { COLOR_WHITE=1, COLOR_BLACK=2 };
  2. enum Species {
  3. SPECIES_BLACK=1,
  4. SPECIES_WHITE=2,
  5. SPECIES_YELLOW=3
  6. };
  7. if (COLOR_WHITE == SPECIES_BLACK) {
  8. // do something
  9. }

不要以为程序员不会犯这样的错误,当被产品经理的需求踢着屁股跑时,他们可什么事情都干得出来。

不能前向声明

前向声明是隐藏实现和减少代码依赖的有利工具。但是下面的代码会编译报错。

  1. // forward declaration
  2. enum Color;
  3. class A {
  4. public:
  5. void foo(Color c);
  6. private:
  7. // some members ..
  8. };

C++的编译过程其实是内存布局的过程。当我们前向声明class时,会使用指针或引用,内存大小固定是8个字节(64位机器)。但这里使用的是Color本身,而枚举的大小其实是由实现决定的。

  1. enum Color { COLOR_WHITE = 1, COLOR_BLACK = 2 };
  2. // sizeof(Color) == 4
  3. enum Color { COLOR_WHITE = 100000000000, COLOR_BLACK = 2 };
  4. // sizeof(Color) == 8

看到没有,一般情况下枚举的大小是4字节,当枚举值太大时,就会变成8字节。所以编译器并不能仅靠前向声明下判断。

3. C++11的枚举

C++11提出了enum class,也称为strong typed enum,它解决了老式枚举的三个问题

名字冲突

  1. enum class Color {
  2. WHITE=1, BLACK=2 };
  3. enum class Species {
  4. BLACK=1. WHITE=2, YELLOW=3, BROWN=4 };
  5. // 使用时,枚举类型名为上层命名空间
  6. // Color::WHITE
  7. // Color::BLACK
  8. // Species::BLACK
  9. // Species::YELLOW

类型安全

枚举不能直接和数字比较

  1. // 编译报错
  2. if (Color::WHITE == 1) {
  3. // do something
  4. }

不同的枚举不能比较

  1. // 编译报错
  2. if (Color::WHITE == Species::WHITE) {
  3. // do something
  4. }

前向声明

enum class 可以指定底层的实现,所以编译器就知道枚举的大小了。

  1. // 成功编译,哈哈
  2. // 还可以指定 int, unsigned int, short等,整数类型都可以
  3. enum class Color : char;
  4. class A {
  5. public:
  6. void foo(Color c);
  7. };
  8. enum class Color : char { WHITE=1, BLACK=2 };
  9. // sizeof(Color) == 1

4. C++11 提醒

虽然enum class做了很多改进,但是并不完美,还是有很多值得注意的地方。

enum class不是类

新枚举不是class,而是一个单独的类型,更像是整数类型的封装。它没有像类一样的构造和析构机制。如果定义一个未指定值的枚举,那么默认值是0,即使定义中没有0

  1. enum class Color { WHITE=1, BLACK=2 };
  2. Color c;
  3. static_cast<int>(c); // it's 0

enum class不能定义方法

这是我怨念的地方。有时候我想把枚举以字符串的形式打印出来,可读性更好。如果能像下面一样写代码多好,可惜enum class不支持定义任何方法。

  1. Color c = Color::WHITE
  2. // 不可能如此调用
  3. c.toString() // "white"

于是只能用单独的函数实现了,代码的组织不够紧凑,也没够美观。

  1. const char* ColorToString(Color c)
  2. {
  3. switch (c) {
  4. case Color::WHITE:
  5. return "white";
  6. case Color::BLACK:
  7. return "black";
  8. default:
  9. abort(); // 可能是未初始化的枚举,这属于逻辑错误
  10. }
  11. }

5. 参考资料

Bjarne Stroustrup’s C++11 FAQ
Better types in C++11 - nullptr, enum classes (strongly typed enumerations) and cstdint
StackOverflow: Why is enum class preferred over plain enum?
本文转自: C++的枚举
原文作者:wankai

发表评论

表情:
评论列表 (有 0 条评论,341人围观)

还没有评论,来说两句吧...

相关阅读

    相关 C#——类型

    C\——枚举类型 枚举类型 是由基础整型数值类型的一组命名常量定义的值类型。 若要定义枚举类型,请使用 enum 关键字并指定枚举成员 的名称: enum Se

    相关 C#——

    枚举 ![0_13315183659D8L.gif][] > 声明枚举的条件:确定数量,确定值的取值范围。 > > 枚举的语法:(1)声明枚举的时候和类同级。 > >

    相关 C++

    1. 介绍 第一次学习枚举类型时,觉得这个名字很诡异。但是后来发现,“枚举”真的特别传神,枚举就是可数的意思。 当你发现某个类型的值是数得过来的,那就派枚举出场吧。

    相关 C#】

    简单总结一下枚举的用法: 什么是枚举? 枚举简单的说是一种数据类型,只不过这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。举个例子,颜色也可以