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 dobreak;
}

“计数”值

您可能会遇到在末尾添加“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.mzph.cn/news/197985.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android wifi Enable之后扫描流程

流程框架图 通常我们在设备开启wifi之后&#xff0c;等会会自动扫描出周围的热点。 下面看下自动扫描周围热点的流程 代码流程 1. ClientModeManager.ClientModeStateMachine ClientModeStateMachine 由CMD_START 转换到StartedStateStartedState 状态机&#xff0c;在更新…

ubuntu内移除snap

ubuntu 移除snap snap 是 ubuntu 的集中式管理的软件商城&#xff0c;但在使用过程中软件更新没有很及时。ubuntu 20.04 在安装开始就有预装了 snap&#xff0c;所以我想把它移除掉&#xff0c;省出系统空间。 具体参考链接&#xff1a;https://cloud.tencent.com/developer/…

让代码变得优雅简洁的神器:Java8 Stream流式编程

原创/朱季谦 本文主要基于实际项目常用的Stream Api流式处理总结。 因笔者主要从事风控反欺诈相关工作&#xff0c;故而此文使用比较熟悉的三要素之一的【手机号】黑名单作代码案例说明。 我在项目当中&#xff0c;很早就开始使用Java 8的流特性进行开发了&#xff0c;但是一直…

关于区块链

一、区块链 区块链技术是一种去中心化、不可篡改、安全可靠的分布式账本技术&#xff0c;应用场景广泛&#xff0c;有以下几个方面的优势&#xff1a; 金融领域&#xff1a;区块链技术可以用于实现安全的数字货币交易&#xff0c;如比特币等。同时&#xff0c;区块链技术可以用…

流媒体方案之FFmpeg——实现物联网视频监控项目

目录 前言 一、FFmpeg介绍 二、FFmpeg简易理解 三、FFmpeg的重要概念 四、软硬件准备 五、移植、运行FFmpeg 六、运行FFmpeg 前言 最近想做一个安防相关的项目&#xff0c;所以跟着韦东山老师的视频来学习视频监控方案的相关知识&#xff0c;韦东山老师讲的课非常好&…

LaTex入门简明教程

文章目录 写在前面安装Texlive的安装TeXstudio 的安装 LaTex 的使用节指令图指令表指令公式指令参考文献指令引用指令TeXstudio 编译 LaTex 的 \label{} 写法建议最后 写在前面 这篇文章面向没有任何 LaTex 基础的小白&#xff0c;主要讲解了 LaTex 的安装和使用。读完文章之后…

enum 枚举类型

我们可以通过定义枚举IpAddrKind来表达这样的概念&#xff0c;声明该枚举需要列举出所有可能的IP地址种类&#xff0c;也就是所谓的枚举变体&#xff08;variant&#xff09;。 enum IpAddrKind {V4,V6, }枚举的变体全部位于其标识符的命名空间中&#xff0c;并使用两个冒号来…

android https 证书过期

有的时候 我们android https 证书过期 &#xff0c;或者使用明文等方式去访问服务器 可能会碰到类似的 问题 &#xff1a; javax.net.ssl.SSLHandshakeException: Chain validation failed java.security.cert.CertPathValidatorException: Response is unreliable: its validi…

通讯录管理系统(基于C语言)

模块设计 本通讯录管理系统功能模块共包括9个部分&#xff1a;1.输入数据、2.显示数据、 3.插入数据、4.删除数据、5.查看数据、6.修改数据、7.保存数据、 8.返回主菜单、9.退出系统. 一&#xff0e;总体设计 通讯录的每一条信息包括&#xff1a;姓名、性别、住址、联系电话…

西南科技大学模拟电子技术实验七(集成运算放大器的非线性应用)预习报告

一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) 预习计算内容根据运放的非线…

【MODBUS】Modbus主站云端服务器和边缘设备部署区别

Modbus主站作为云端服务器&#xff1a; 云端服务器作为主站&#xff1a; 在这种部署方式中&#xff0c;云端服务器充当Modbus通信的主站&#xff0c;负责向不同的Modbus从站发起请求&#xff0c;并处理响应。云端服务器通常与其他云服务一起运行&#xff0c;可以在云平台上实现…

【Linux下如何生成coredump文件】

一&#xff0c;什么是coredump 我们经常听到大家说到程序core掉了&#xff0c;需要定位解决&#xff0c;这里说的大部分是指对应程序由于各种异常或者bug导致在运行过程中异常退出或者中止&#xff0c;并且在满足一定条件下&#xff08;这里为什么说需要满足一定的条件呢&#…

QT使用SQLite(打开db数据库以及对数据库进行增删改查)

QTSQLite 在QT中使用sqlite数据库&#xff0c;有多种使用方法&#xff0c;在这里我只提供几种简单&#xff0c;代码简短的方法&#xff0c;包括一些特殊字符处理。 用SQlite建立一个简单学生管理数据库 数据库中有两个表一个是class和student。 class表结构 student表结果…

Java零基础-if条件语句

前言 条件语句是编程语言中最基础也是最常用的语句之一&#xff0c;对于初学者来说&#xff0c;掌握好条件语句是学习编程的第一步。本文将以Java开发语言为例&#xff0c;详细介绍Java中的if条件语句及其应用场景。 摘要 本文主要包含以下内容&#xff1a; Java中的if条件…

非标设计之气缸类型

空压机&#xff1a; 空压机又称空气压缩机&#xff0c;简单来说就是将机械能转化为压力能来进行工作的&#xff0c;空压机在电力行业应用比较多&#xff0c;除了在电力行业应用较多外&#xff0c;其实空压机还有一个比较常见的用途就是用来制冷和分离气体&#xff0c;输送气体…

【web安全】RCE漏洞原理

前言 菜某的笔记总结&#xff0c;如有错误请指正。 RCE漏洞介绍 简而言之&#xff0c;就是代码中使用了可以把字符串当做代码执行的函数&#xff0c;但是又没有对用户的输入内容做到充分的过滤&#xff0c;导致可以被远程执行一些命令。 RCE漏洞的分类 RCE漏洞分为代码执行…

Vue子组件的挂载,以及使用子组件的原理

Vue子组件是如何挂载的&#xff1f; vue中&#xff0c;子组件是通过父组件模板中的标签来进行挂载的&#xff0c;当父组件模板中包含子组件的标签时&#xff0c;vue会在渲染过程中自动创建并挂载子组件 挂载过程如下&#xff1a; 1、解析父组件模板&#xff1a;当vue实例化父…

RT-Thread 三步实现利用DMA进行串口发送

应某些网友需求&#xff0c;说网上根本找不到基于Rt-Thread DMA串口发送代码&#xff0c;只有官方开源的串口DMA接收。 其实这些东西并不难&#xff0c;只要你细心去看哪些闲置的驱动文件且都是包装好的&#xff0c;通过关键字去查询或点开源文件查看&#xff0c;花不了几分钟…

【C/PTA —— 14.结构体1(课内实践)】

C/PTA —— 14.结构体1&#xff08;课内实践&#xff09; 6-1 计算两个复数之积6-2 结构体数组中查找指定编号人员6-3 综合成绩6-4 结构体数组按总分排序 6-1 计算两个复数之积 struct complex multiply(struct complex x, struct complex y) {struct complex product;product.…

Selenium 自动化高级操作与解决疑难杂症,如无法连接、使用代理等

解决 Selenium 自动化中的常见疑难杂症 这里记录一些关于 Selenium的常用操作和疑难杂症。 有一些细节的知识点就不重复介绍了&#xff0c;因为之前的文章中都有&#xff01; 如果对本文中的知识点有疑问的&#xff0c;可以先阅读我以前分享的文章&#xff01; 知识点&…