C 中的枚举

简要回顾

最简单的枚举是比宏稍微高级一点的东西。它们可以避免像这样做:

#define COLOR_BLACK 0
#define COLOR_WHITE 1
#define COLOR_BLUE  2
#define COLOR_GREEN 3
#define COLOR_RED   4

你可以这样做:

enum color {
  COLOR_BLACK,
  COLOR_WHITE,
  COLOR_BLUE,
  COLOR_GREEN,
  COLOR_RED,    // Extra ',' here is allowed.
};

在声明一个枚举时,编译器允许你在最后一个常量后面加一个逗号,作为一种便利。

你可以使用color作为一个类型,使用枚举常量作为值:

enum color c = COLOR_BLACK;

枚举的基本思想是使用它们来表达一组相关值。

命名空间和声明

与结构体和联合类似,枚举类型被放在一个单独的“标签”命名空间中,所以你必须继续使用enum前缀。同样地,你也可以使用typedef来将枚举标签“导入”到全局命名空间中:

typedef enum color color;
color c = COLOR_BLUE;     // Don't need "enum" now.

然而,与结构体和联合不同,枚举不允许前向声明:

struct node;     // OK: forward declaration.
struct node *p;  // OK: pointer to node.
enum color;      // Error: forward declaration not allowed.

调试器优势

枚举的一个直接优势是调试器能够理解它们,并打印它们的常量名,而不是它们的底层整数值:

(gdb) p c
$1 = COLOR_BLUE

这比如果c只是一个int,你必须查找颜色2对应的是什么要好得多。

名称冲突

如果您不熟悉 C 中的枚举,您可能想知道为什么使用冗长的常量名称。可以更简单一点:

enum color {
  BLACK,
  WHITE,
  BLUE,
  GREEN,
  RED,
};

枚举常量没有作用域,这意味着它们都被“注入”到全局命名空间中。如果您还有另一个枚举,例如:

enum rb_color {  // Red-Black tree node color.
  BLACK,         // Error: redefinition of 'BLACK'.
  RED            // Error: redefinition of 'RED'.
};

那么你会得到重新定义错误。因此,最佳实践是使用公共前缀命名同一枚举的所有常量,并希望它们不会与其他地方的其他名称发生冲突。

这个问题在 C++11 中已通过作用域枚举修复,但尚未向后移植到 C(如果有的话)。

基础类型

每个枚举都有一个基础类型,即在机器层面实际用来表示它的类型。它通常是int,但可以是任何足够大的整数类型,能够容纳最大的常量值。

在C23之前,没有办法知道基础类型是什么,也没有办法显式地指定它。(你最多可以通过sizeof知道它的大小。)然而,在C23中,你可以通过在枚举类型的名称后面加上一个冒号和底层类型来显式地指定它,例如:
enum color : unsigned char {  // C23 and later only.
  // ...
};
如果你想保证一个比int更小或更大的大小,并在表达式中控制它的符号,这是很有用的。基础类型可以是任何int或char类型(有符号或无符号)或它们的typedef。

隐式转换

枚举常量和变量在表达式中隐式地转换为它们的底层类型的值。另外,底层类型的值也隐式地转换为枚举类型。虽然这些转换有时候很方便,但它们也允许写出没有错误也没有警告的无意义的代码: 幸运的是,隐式转换也有更好的用途——稍后会详细介绍。

color c = COLOR_BLACK + COLOR_WHITE * 2;  // ???

Values

枚举常量的值由编译器分配(默认情况下),从 0 开始,每个常量加 1。通常,你并不特别关心这些值实际上是什么。

但是,您可以显式指定所有或仅某些常量的任何值。您甚至可以指定负值(除非您指定了unsigned基础类型)。如果省略,常量的值将由编译器指定为前一个值加一:

enum color {
  COLOR_NONE   = -1,
  COLOR_BLACK  = 0,
  COLOR_WHITE  = 1,
  COLOR_BLUE,        // Value is 2 ...
  COLOR_GREEN,       // ... 3 ...
  COLOR_RED,         // ... 4 ...
};

然而,你不应该显式地指定值,除非以下情况之一成立:

  • 值是“外部强制的”或者有其他含义;
  • 你需要“序列化”值(无论是在磁盘上还是“通过网络”);
  • 你是在表示位标志。

外部施加值 

外部施加值的一个示例是,如果您正在为图形终端编写软件,而硬件使用特定的值来表示特定的颜色

enum ansi_color {
  ANSI_BLACK  = 40,
  ANSI_WHITE  = 47,
  ANSI_BLUE   = 44,
  ANSI_GREEN  = 42,
  ANSI_RED    = 41
};

由于隐式转换为整数,您可以直接使用这些值:

printf( "\33[%dm", ANSI_RED );  // Will print in red.

序列化值

如果您将值写入磁盘(可能是为了在稍后的时间读回它们),您需要确保 3 始终对应于 COLOR_GREEN,即使您添加了更多颜色。如果未明确指定这些值,并且您在除末尾之外的任何位置添加了新颜色,则后续值将默默地移动 1:
enum color {
  COLOR_BLACK,
  COLOR_WHITE,
  COLOR_YELLOW,  // New color is now 2.
  COLOR_BLUE,    // This used to be 2, but is now 3 ...
  COLOR_GREEN,   // ... and so on.
  COLOR_RED
};

当然,您可以制定始终在最后添加新值的策略,但这依赖于程序员遵循该策略。如果您显式指定值,编译器可以帮助您强制执行唯一值,但不是以您可能假设的方式 - 稍后会详细介绍。

或者,您可以将值序列化为字符串:

void write_color( color c, FILE *f ) {
  switch ( c ) {
    case COLOR_BLACK: fputs( "black", f ); return;
    case COLOR_WHITE: fputs( "white", f ); return;
    case COLOR_BLUE : fputs( "blue" , f ); return;
    case COLOR_GREEN: fputs( "green", f ); return;
    case COLOR_RED  : fputs( "red"  , f ); return;
  }
  UNEXPECTED_VALUE( c );
}

虽然序列化为文本的成本更高,但如果您将其余数据序列化为 JSON 等文本格式,那么这并不重要。另一个优点是基本值的改变并不重要。

UNEXPECTED_VALUE( c ) 是一个宏,如下所示:
#define UNEXPECTED_VALUE(EXPR) do {                    \
    fprintf( stderr,                                   \
      "%s:%d: %lld: unexpected value for " #EXPR "\n", \
      __FILE__, __LINE__, (long long)(EXPR)            \
    );                                                 \
    abort();                                           \
  } while (0)

如果你想进行防御性编程,你可以使用它(或类似的东西)。

重复值

具有相同基础值的同一枚举的两个常量是完全合法的:

enum color {
  // ...
  COLOR_GREEN,
  COLOR_CHARTREUSE = COLOR_GREEN,
  // ...
};

它们是同义词。在这种情况下,这显然是故意的。但是,可能会意外引入同义词,尤其是在具有大量显式提供的值的枚举中。由于同义词是合法的,编译器无法帮助您检测意外的同义词 - 直到您switch使用它们:

switch ( c ) {
  // ...
  case COLOR_GREEN:
    // ...
    break;
  case COLOR_CHARTREUSE:  // Error: duplicate case value.
    // ...

“无”值

如果枚举可以具有“默认”、“确定”、“无”、“未设置”、“未指定”或类似值,则应首先声明它,如下所示:

  1. 默认情况下,编译器会为其分配值 0,这在调试器中很容易识别。
  2. 全局或文件范围的static枚举变量将自动初始化为 (0)。

例如:

enum eol {
  EOL_UNSPECIFIED,
  EOL_UNIX,
  EOL_WINDOWS
};

检查值

如果您需要检查枚举变量的值是否有一个特定值,可以使用 if :
if ( eol == EOL_UNSPECIFIED )
  return;

但是,如果您需要检查多个值,则应始终使用switch

switch ( eol ) {
  case EOL_UNSPECIFIED:  // Default to Unix-style.
  case EOL_UNIX:
    putchar( '\n' );
    break;
  case EOL_WINDOWS:
    fputs( "\r\n", stdout );
    break;
}

为什么要这样做呢?因为如果你在switch语句中漏掉了一个常量的case,编译器会给你一个警告。这非常有用,如果你添加了一个新的枚举常量:编译器可以告诉你你在哪些switch语句中漏掉了case。

但是,你应该避免在switch语句中使用default,因为它会阻止编译器能够警告你当你漏掉了一个常量的case。最好是为每个常量都写一个case,即使这些case什么都不做:

switch ( ast->array.kind ) {
  case C_ARRAY_INT_SIZE:
    dup_ast->array.size_int = ast->array.size_int;
    break;
  case C_ARRAY_NAMED_SIZE:
    dup_ast->array.size_name = strdup( ast->array.size_name );
    break;
  case C_ARRAY_EMPTY_SIZE:  // Don't use "default" here.
  case C_ARRAY_VLA_STAR:
    // nothing to do
    break;
}

“计数”值

您可能会遇到在末尾添加“count”常量的代码:

enum color {
  COLOR_BLACK,
  COLOR_WHITE,
  COLOR_BLUE,
  COLOR_GREEN,
  COLOR_RED,
  NUM_COLOR    // Equal to number of colors (here, 5).
};

这样做的目的是让NUM_COLOR的底层值表示颜色的数量,因为编译器会自动给它赋值为5,这是实际颜色的数量。这样就可以通过使用底层值作为数组的索引(假设第一个常量的值是0)来简化文本的序列化过程:

void write_color( color c, FILE *f ) {
  static char const *const COLOR_NAME[] = {
    "black",
    "white"
    "blue",
    "green",
    "red"
  };
  if ( c >= NUM_COLOR )     // Defensive check.
    UNEXPECTED_VALUE( c );
  fputs( COLOR_NAME[ c ], f );
}

这样做的弊端是,它会添加一个“假的颜色”值,你需要在每个switch语句中把这个值作为一个case,以避免编译器警告没有处理的case,即使这个值永远不会匹配。正因为如此,我不推荐在枚举中添加“count”常量。

位标志值

另一种使用枚举的方式是定义一组位标志,其中每个常量都是一个不同的2的幂:

enum c_int_fmt {
  CIF_NONE     = 0,
  CIF_SHORT    = 1 << 0,
  CIF_INT      = 1 << 1,
  CIF_LONG     = 1 << 2,
  CIF_UNSIGNED = 1 << 3,
  CIF_CONST    = 1 << 4,
  CIF_STATIC   = 1 << 5,
};

通常的做法是使用N是从 0 到需要多少位的第 N 位,而1 << N不是显式指定 2 的幂值,例如 0、1、2、4、8 等,并让编译器为你计算一下。

然后您可以按位或将各个标志组合在一起:

c_int_fmt f = CIF_CONST | CIF_UNSIGNED | CIF_INT;

这会导致一个值 ( 0b0011010) 不在声明的常量范围内 — 但这是完全合法的。调试器甚至足够聪明,可以注意到这一点并相应地打印:

(gdb) p f
$1 = CIF_INT | CIF_UNSIGNED | CIF_CONST

您还可以测试是否包含特定位:

if ( (f & CIF_STATIC) != CIF_NONE )
  puts( "static " );
if ( (f & CIF_CONST) != CIF_NONE )
  puts( "const " );
if ( (f & CIF_UNSIGNED) != CIF_NONE )
  puts( "unsigned " );
if ( (f & CIF_SHORT) != CIF_NONE )
  puts( "short " );
if ( (f & CIF_LONG) != CIF_NONE )
  puts( "long " );
if ( (f & CIF_INT) != CIF_NONE )
  puts( "int " );

或者测试位组,例如,确实f有两个特定的位组:

if ( (f & (CIF_SHORT | CIF_LONG)) == CIF_SHORT | CIF_LONG )
  goto illegal_format;

当然,需要注意的是,switch此类枚举上的 a 可能不匹配任何case。尽管如此,枚举通常用于按位标志。

在这种情况下,隐式转换为int很方便,因为按位运算符可以正常工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/157630.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++面试宝典第2题:逆序输出整数

题目 写一个方法&#xff0c;将一个整数逆序打印输出到控制台。注意&#xff1a;当输入的数字含有结尾的0时&#xff0c;输出不应带有前导的0。比如&#xff1a;123的逆序输出为321&#xff0c;8600的逆序输出为68&#xff0c;-609的逆序输出为-906。 解析 这道题本身并没有什么…

03_W5500TCP_Client

上一节我们完成了W5500网络的初始化过程&#xff0c;这节我们进行TCP通信&#xff0c;w5500作为TCP客户端与电脑端的TCP_Server进行通信。 目录 1.TCP通信流程图&#xff1a; tcp的三次握手&#xff1a; tcp四次挥手&#xff1a; 2.代码分析&#xff1a; 1.TCP通信流程图&am…

视频的关键知识

1 引言 视频技术发展到现在已经有100多年的历史&#xff0c;虽然比照相技术历史时间短&#xff0c;但在过去很长一段时间之内都是最重要的媒体。 由于互联网在新世纪的崛起&#xff0c;使得传统的媒体技术有了更好的发展平台&#xff0c;应运而生了新的多媒体技术。而多媒体技…

STM32使用SIM900A、SIM800C、SIM800A完成短信发送、连接onenet上传数据、拨打电话_完整教程

一、前言 本篇文章介绍SIM800C 、SIM800A、SIM900A 等等系列的模块的常用AT指令,讲解模块的使用方法,演示短信发送、拨打电话、网络连接,与服务器通信等常用案例。 如果只是用到发送短信、拨打电话、连接网络通信、这些模块的AT指令是兼容的。 文章最后贴了完整的STM32代码…

go写文件后出现大量NUL字符问题记录

目录 背景 看看修改前 修改后 原因 背景 写文件完成后发现&#xff1a; size明显也和正常的不相等。 看看修改前 buf : make([]byte, 64) buffer : bytes.NewBuffer(buf)// ...其它逻辑使得buffer有值// 打开即将要写入的文件&#xff0c;不存在则创建 f, err : os.Open…

家用小型洗衣机哪款性价比高?内衣洗衣机品牌推荐

近日&#xff0c;国内著名的电子商务平台公布了“内衣洗衣机产业趋势”的研究报告。该报告指出&#xff0c;由于消费者对生活质量的要求越来越高&#xff0c;内衣洗衣机的行业也有了长足的发展&#xff0c;特别是在今年以来&#xff0c;内衣洗衣机的销售额同比上涨了830%&#…

ESP32 freeRTOS笔记 参数传递、任务优先级

一、四种参数传递方式 1.1 整数传递 使用 (void *) 任何类型传递参数&#xff0c;通过地址传递给任务。 #include <stdio.h> #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h"void myTask(void *pvP…

【每日一题】从二叉搜索树到更大和树

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;中序遍历的反序方法二&#xff1a;后缀数组 写在最后 Tag 【中序遍历】【二叉树】【2023-12-04】 题目来源 1038. 从二叉搜索树到更大和树 题目解读 在二叉搜索树中&#xff0c;将每一个节点的值替换成树中大于等于该…

仅 CSS 阅读进度条

为了构建一个阅读进度条&#xff0c;即显示用户向下滚动时阅读文章的进度&#xff0c;很难不考虑 JavaScript。但是&#xff0c;事实证明&#xff0c;您也可以使用纯 CSS 构建阅读进度条。 从本质上讲&#xff0c;一个名为 animation-timeline 的新实验性 CSS 属性可以让你指定…

【23-24 秋学期】NNDL 作业12 优化算法2D可视化

简要介绍图中的优化算法&#xff0c;编程实现并2D可视化 1. 被优化函数 2. 被优化函数 3. 解释不同轨迹的形成原因 分析各个算法的优缺点 REF&#xff1a;图灵社区-图书 (ituring.com.cn) 深度学习入门&#xff1a;基于Python的理论与实现 NNDL 作业11&#xff1a;优化算…

【蓝桥杯软件赛 零基础备赛20周】第5周——高精度大数运算与队列

文章目录 1. 数组的应用–高精度大数运算1.1 Java和Python计算大数1.2 C/C高精度计算大数1.2.1 高精度加法1.2.2 高精度减法 2. 队列2.1 手写队列2.1.1 C/C手写队列2.1.2 Java手写队列2.1.3 Python手写队列 2.2 C STL队列queue2.3 Java队列Queue2.4 Python队列Queue和deque2.5 …

HostHunter虚拟主机发现

HostHunter虚拟主机发现 1.HostHunter2.安装3.参数解释4.实例1.HostHunter HostHunter 一种工具,用于有效发现和提取提供大量目标 IPv4 或 IPv6 地址的主机名。HostHunter 利用简单的 OSINT 和主动协调技术将 IP 目标与虚拟主机名进行映射。这对于发现组织的真正攻击面特别有…

12、pytest上下文友好的输出

官方实例 # content of test_assert2.py import pytestdef test_set_comparison():set1 set("1308")set2 set("8035")assert set1 set2def test_dict_comparison():dict_1 {name:陈畅,sex:男}dict_2 {name:赵宁,sex:女}assert dict_1 dict_2def tes…

YoloV5改进策略:Swift Parameter-free Attention,无参注意力机制,超分模型的完美迁移

摘要 https://arxiv.org/pdf/2311.12770.pdf https://github.com/hongyuanyu/SPAN SPAN是一种超分网络模型。SPAN模型通过使用参数自由的注意力机制来提高SISR的性能。这种注意力机制能够增强重要信息并减少冗余,从而在图像超分辨率过程中提高图像质量。 具体来说,SPAN模…

VSCode Vue 开发环境配置

Vue是前端开发中的重要工具与框架&#xff0c;可以保住开发者高效构建用户界面。 Vue2官方文档&#xff1a;https://v2.cn.vuejs.org/ Vue3官方文档&#xff1a;https://cn.vuejs.org/ Vue的安装和引用 Vue2的官方安装指南&#xff1a;https://v2.cn.vuejs.org/v2/guide/ins…

clip-path,css裁剪函数

https://www.cnblogs.com/dzyany/p/13985939.html clip-path - CSS&#xff1a;层叠样式表 | MDN 我们看下这个例子 polygon里有四个值分别代表这四个点相对于原图左上方的偏移量。 裁剪个五角星

C语言碎片知识

sizeof 1.sizeof是C语言中的一个操作符&#xff0c;同时也是关键字&#xff01;&#xff01;&#xff01;&#xff01; 2.sizeof的操作数可以是类型&#xff0c;变量或表达式 如图&#xff0c;第一个为什么是6&#xff1f;&#xff0c;因为先计算了3的大小&#xff0c;占4个字…

陀螺仪LSM6DSV16X与AI集成(2)----姿态解算

陀螺仪LSM6DSV16X与AI集成.2--姿态解算 概述视频教学样品申请完整代码下载欧拉角万向节死锁四元数法姿态解算双环PI控制器偏航角陀螺仪解析代码上位机通讯加速度演示陀螺仪工作方式主程序演示 概述 LSM6DSV16X包含三轴陀螺仪与三轴加速度计。 姿态有多种数学表示方式&#xff…

数据结构-栈和队列

文章目录 栈什么是栈栈的操作栈的特点栈的实现栈的时间复杂度栈的应用 队列队列的概念队列的操作队列的实现队列的时间复杂度 栈 什么是栈 堆栈又名栈&#xff08;stack&#xff09;&#xff0c;它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。 栈的操作…

一键式紧急报警柱系统

随着科技的不断发展&#xff0c;一键式紧急报警柱在我们的生活和工作中扮演着越来越重要的角色。在这篇文章中&#xff0c;我们将一起探究与一键式紧急报警柱有关的知识。 一键式紧急报警柱是一种常见的安全防护设备&#xff0c;能够在紧急情况下快速发出警报&#xff0c;保护…
最新文章