查看原文
其他

【C进阶】同事笑我 : 有了"宏"你还用"枚举"干嘛?

bug菌 最后一个bug 2022-07-15

1、聊一聊

    今天跟大家分享一首海上钢琴曲的经典插曲,同样海上钢琴师这部电影也给了bug菌非常多的人生启发,或许每种职业都是相似的吧。


    本文主要跟大家介绍C语言枚举类型的相关实用技巧并纠正一些在理解上出现的误区。



2、情景再现


    小鲁班bug菌,你的代码怎么又是宏又是枚举呀?把我给绕晕了,宏不是可以直接替换掉枚举吗?


bug菌   小鲁班,照你这么说那枚举类型都不用定义在C标准里面了,直接把枚举删除.


    小鲁班咦,好像有那么一丢丢道理哦,可是我平时编码都不用枚举的,感觉没啥用呀。


bug菌   咳咳,今天跟你好好讲讲,枚举可好用了!



01
定义出发


     宏与枚举最明显的区别在于宏仅仅只是一个在预编译阶段的文本替换的标签,而枚举是一种跟int、float等等一样的数据类型,那么既然是一种数据类型枚举类型到底有多大,并且占据多少个字节呢?


    bug菌写个简单的demo测试下:

参考demo:
#include <stdio.h>

typedef enum UART
{
    UART1,
    UART2,
    UART3
}eUART; 


typedef enum CAN
{
    CAN1 = 1,
    CAN2
}eCAN; 

/************************************
 * Author:(公众号:最后一个bug) 
 * Fuction:测试枚举类型 
 ***********************************/

int main(int argc, char *argv[]) {

    printf("UART1 = %d\n",UART1);
    printf("UART2 = %d\n",UART2);
    printf("UART3 = %d\n",UART3);

    printf("CAN1  = %d\n",CAN1);
    printf("CAN2  = %d\n",CAN2);

    printf("sizeof(eUART) = %d\n",sizeof(eUART));
    return 0;
}
输出结果:

解释一下:
  • 通过上面的例子我们了解到,默认枚举值从0开始,并且当一旦定义了一项枚举类型便会自动递增处理。

  • 同时枚举类型在32位平台上其占据着四个字节的内存空间,当然这仅仅是编译器缺省状态,编译器完全可以根据枚举类型的最大数值进行优化字节处理。


02
枚举注意事项


     1 )一个枚举类型内名称必须不同,可是数值可以相同,且默认递增机制为从赋值处开始依次往后增加,所以大家在同一个枚举类型中尽量不要定义相同的数值,以免造成混乱。


     2 )不同枚举类型,其枚举元素名称也不能相同,否则编译失败。


参考demo:
#include <stdio.h>

typedef enum CAN
{
    CAN1 = 1,
    CAN2,
    CAN3,
    CAN4 = 1,
    //CAN4, 名称不能相同 
    CAN5,
    CAN8 = 0xFFFFFFFFFF
}eCAN; 

typedef enum UART
{
    //CAN1 = 1, 前面已经定义了,这里不能定义 
     //CAN2,
    UART1 = 1,
     UART2,
}eUART; 

/************************************
 * Author:(公众号:最后一个bug) 
 * Fuction:测试枚举类型 
 ***********************************/

int main(int argc, char *argv[]) {

    printf("CAN1  = %d\n",CAN1);
    printf("CAN2  = %d\n",CAN2);
    printf("CAN5  = %d\n",CAN5);
    printf("CAN8  = 0x%X\n",CAN8);
    return 0;
}
运行结果:


    3 )枚举元素与宏重名你可要注意了,编译器优先会进行宏替换,如下面的代码示例。

参考demo:
#include <stdio.h>

//#define CAN3 5  //编译失败 

typedef enum CAN
{
    CAN1 = 1,
    CAN2,
    CAN3,
}eCAN; 


#define CAN3 5  //编译通过,有限使用宏 

/************************************
 * Author:(公众号:最后一个bug) 
 * Fuction:测试枚举类型 
 ***********************************/

int main(int argc, char *argv[]) {

    printf("CAN1  = %d\n",CAN1);
    printf("CAN2  = %d\n",CAN2);
    printf("CAN5  = %d\n",CAN3);
    return 0;
}
输出结果:

解释一下:
  • 如果重名宏在枚举前面会报编译错误,而如果在枚举后面,则会生效,且优先使用。

  • 如果你代码不规范,把枚举元素单独赋值给一个整形变量如int Val= CAN3,这样后续使用Val的代码是非常危险的。

    小鲁班哇,bug菌,这些我平时还真没注意,看别人代码的时候这些应该有用,不过我全程用宏不用枚举不就行了吗?


bug菌   你不这样写,不代表别人不会这样写,维护人家的代码也是非常头疼的,所以先记下来哦,下面我再给你演示几波操作,你看好了!


3、枚举类型妙用

   于枚举类型bug菌整理了两种平时使用得比较多的地方。


1

传参保护


    对于枚举变量其取值均来自其所枚举的元素,这样对于函数传递参数等起到限定保护作用,同时枚举元素自动累加方便后续扩展定义。


    当然有些小伙伴会使用强制类型转换,甚至有些编译平台可以直接赋值,但建议尽量少用,如果一定要用,务必明确对应平台上枚举类型的大小,否则会存在大值给小值带来的数据范围问题。

参考demo:
#include <stdio.h>

typedef enum Dev
{
    UART1 = 1,
    UART2,
    CAN1,
    SPI
}eDev; 

/************************************
 * Author:(公众号:最后一个bug) 
 * Fuction:SendDevData1--采用枚举 
 ***********************************/

int SendDevData1(eDev dev,unsigned char *buff,unsigned char size)
{
    return 1;   

/************************************
 * Author:(公众号:最后一个bug) 
 * Fuction:SendDevData2--采用整形 
 ***********************************/

int SendDevData2(int dev,unsigned char *buff,unsigned char size)
{
    return 1;

/************************************
 * Author:(公众号:最后一个bug) 
 * Fuction:枚举变量限定范围 
 ***********************************/

int main(int argc, char *argv[]) {


    return 0;
}

2

作为数组下标


    这种枚举使用技巧应该是大家最常用的,用枚举作为下标能够在一定程度上降低越界的风险,同时也方便扩展,如下面代码。

参考伪代码:
#include <stdio.h>

typedef enum Dev
{
    UART1 = 0,
    UART2,
    CAN1,
    SPI,
    MAX_DEV
}eDev; 

sDevInfo stDevInfo[MAX_DEV];

/************************************
 * Author:(公众号:最后一个bug) 
 * Fuction:枚举类型作为下标 
 ***********************************/

int main(int argc, char *argv[]) {

    int DevIndex = 0;
    //初始化操作 
    for(DevIndex = 0;DevIndex < MAX_DEV;DevIndex++)
    {
        stDevInfo[DevIndex].Initial();
    }
    //平时使用操作 
    stDevInfo[UART1].SendData(buff,size);
    stDevInfo[UART2].SendData(buff,size);
    stDevInfo[CAN1].SendData(buff,size);
    stDevInfo[SPI].SendData(buff,size);

    return 0;
}
分析一下:
  • 枚举默认从0开始并且当不强制赋值元素依次递增,与数组下标是天然对应的,上面通过定义MAX_DEV就能够降低数据越界风险,且平时统一都使用枚举来索引数组也会更加方便、易懂。

  • bug菌强调一点的是,作为数组下标一定不要在枚举类型定义中间强制赋值跳跃数值,否则容易引入一些不必要的麻烦。


3

封装底层

    

    在很早期的MCU系列文章中bug菌谈到过库函数的实现技巧,其中也提到了枚举类型对于封装寄存器是比较好用的,因为大部分寄存器设置值都是0x00,0x01,0x02等等这样的实现,通过把这些数据整理成枚举类型再合适不过了。



    下面是ST库的封装,看来枚举的使用是非常契合的,以后自己封装也可以借鉴。



bug菌   小鲁班,上面三个小技巧能不能说服你开始使用枚举了?

    小鲁班bug菌,学废了,不用你提醒,给你三连!

5、结束语

    枚举个人觉得还是挺好用的,至少bug菌代码里面会经常用起来,当然本文主要讲解枚举,就稍微弱化了宏,当然宏的作用就更不用多说了,大家根据上面的几种情况合理的选择才是最重要的。


    好了,这里是公众号:“最后一个bug”,一个为大家打造的技术知识提升基地,如果你喜欢交流可以添加下方bug菌微信,我拉你加入公众号技术交流群。

推荐好文  点击蓝色字体即可跳转

【开源】bug菌把"动态数字显示"开源了!

【嵌入式】bug粉碎机之volatile的那些坑

【C进阶】拿着"sizeof这些用法和坑"去吹牛吧!

☞ 【进阶】同事用#include"xxx.c"把我给惊呆了!!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存