0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

宏的高级用法

工程师 来源:嵌入式软件实战派 作者:嵌入式软件实战派 2020-09-15 15:59 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

对于条件/分支处理的程序设计,我们惯性地会选择switch-case或者if-else,这也是C语言老师当初教的。以下,我们用一个播放器的例子来说明,要实现的功能如下:

收到用户操作播放器命令请求,如“播放”、“暂停”等,程序要对命令作区分;

针对不同的命令请求,作相应的处理;

输出必要的辅助信息。

首先,将命令定义成enum类型:

enum { CMD_PLAY, CMD_PAUSE, CMD_STOP, CMD_PLAY_NEXT, CMD_PLAY_PREV, };

然后,用switch-case的分支处理:

switch(cmd) { case CMD_PLAY: // handle play command break; case CMD_PAUSE: // handle pause command break; case CMD_STOP: // handle stop command break; case CMD_PLAY_NEXT: // handle play next command break; case CMD_PLAY_PREV: // handle play previous command break; default: break; }

实际上,这也没什么毛病。但是,时间长了,需求不断变更,程序不断迭代,这个switch-case会变得非常冗长而很难维护。你不相信?我曾经见到过》1000行的类似这样的代码。如果让你接手维护这样的代码,你内心会不会狂奔着万千***?

但是,我不敢更改这个祖传的switch-case啊,那么小心翼翼地将这些命令处理封装成函数。像这样:

#define FUNC_IN() printf(“enter %s \r\n”, __FUNCTION__) void func_cmd_play(void* p) { FUNC_IN(); } void func_cmd_pause(void* p) { FUNC_IN(); } void func_cmd_stop(void* p) { FUNC_IN(); } void func_cmd_play_next(void* p) { FUNC_IN(); } void func_cmd_play_prev(void* p) { FUNC_IN(); } void player_cmd_handle(int cmd, void* p) { switch(cmd) { case CMD_PLAY: func_cmd_play(p); break; case CMD_PAUSE: func_cmd_pause(p); break; case CMD_STOP: func_cmd_stop(p); break; case CMD_PLAY_NEXT: func_cmd_play_next(p); break; case CMD_PLAY_PREV: func_cmd_play_prev(p); break; default: break; } }

后来,甲方还是不断地更改需求,导致播放器的命令越来越多,几十个上百个了……痛定思痛,我——要——改——革!!

解放switch-case/if-else

脑子里想来想去,度娘上翻来翻去,于是定义了个结构体:

typedef void(*pFunc)(void* p); typedef struct { tCmd cmd; pFunc func; }tPlayerStruct; tPlayerStruct player_cmd_func[] = { {CMD_PLAY, func_cmd_play) }, {CMD_PAUSE, func_cmd_pause) }, {CMD_STOP, func_cmd_stop) }, {CMD_PLAY_NEXT, func_cmd_play_next) }, {CMD_PLAY_PREV, func_cmd_play_prev) }, }; #define ARR_LEN(arr)sizeof(arr)/sizeof(arr[0]) void player_cmd_handle(int cmd, void* p) { for(int i = 0; i 《 ARR_LEN(player_cmd_func); i++) { if(player_cmd_func[i].cmd == cmd && NULL != player_cmd_func[i].func) { player_cmd_func[i].func(p); break; } } }

咦?好像代码简洁了不少哦,改完之后好有成就感。

身为追求卓越的程序员,我还是有点不满意,可不可以不用for循环,直接使用player_cmd_func[cmd].func(p);,这样还可以免去查询的步骤,提高效率?

想法是好的,如果上面的程序不用for循环,有可能数组越界,还有如果有命令增加,顺序下标不对应的问题。

之前,我在《C语言的奇技淫巧之五》中的第50条提到过这个方法,还立了个flag,我要用MACRO写个更高效更好的代码!

使用X-MACRO

你听说过X-MACRO么?听过没听过都没关系,来,我们一起耍起来!

MACRO或者说宏定义(书上或者规范上一般讲预处理)基本原因都很简单,看看就很容易学会。看起来好像也是平淡无奇,似乎没什么大作用。但是,你可别小看它,我们将其安上个“X”就很牛逼(不知道这个是啥传统,对于某些函数的扩展,喜欢在其前面或后面加个“X”,然后这个函数比之前的函数功能强大很多,Windows里面的Api就有这案例)。

X-MACRO是一种可靠维护代码或数据的并行列表的技术,其相应项必须以相同的顺序出现。它们在至少某些列表无法通过索引组成的地方(例如编译时)最有用。此类列表的示例尤其包括数组的初始化,枚举常量和函数原型的声明,语句序列和切换臂的生成等。X-MACRO的使用可以追溯到1960年代。它在现代C和C ++编程语言中仍然有用。

X-MACRO应用程序包括两部分:

列表元素的定义。

扩展列表以生成声明或语句的片段。

该列表由一个宏或头文件(名为LIST)定义,该文件本身不生成任何代码,而仅由一系列调用宏(通常称为“ X”)与元素的数据组成。LIST的每个扩展都在X定义之前加上一个list元素的语法。LIST的调用会为列表中的每个元素扩展X。

好了,少扯淡,我们是实战派,搞点有用的东西。

对于MACRO有几个明显的特征:

MACRO实际上就是做替换工作;

宏定义的替换工作是在编译前进行的,即预编译;

宏定义可以用undef取消,然后再重新反复定义。

我们就用这几个特征把MACRO耍到牛X起来!

#define X(a,b)a int x = DEF_X(1,2); #undef DEF_X #define DEF_X(a,b)b int y = DEF_X(1,2);

从上面可以看到,这个x和y的值是不一样的。

于是可以定义一个这样的宏:

#define CMD_FUNC \ DEF_X(CMD_PLAY, func_cmd_play) \ DEF_X(CMD_PAUSE, func_cmd_pause) \ DEF_X(CMD_STOP, func_cmd_stop) \ DEF_X(CMD_PLAY_NEXT, func_cmd_play_next) \ DEF_X(CMD_PLAY_PREV, func_cmd_play_prev) \

CMD的enum可以这样定义:

typedef enum { #define DEF_X(a,b) a, CMD_FUNC #undef DEF_X CMD_MAX }tCmd;

预编译后,这实际上就是这样的:

typedef enum { CMD_PLAY, CMD_PAUSE, CMD_STOP, CMD_PLAY_NEXT, CMD_PLAY_PREV, CMD_MAX }tCmd;

接着,我们按这种套路定义一个函数指针数组:

const pFunc player_funcs[] = { #define DEF_X(a,b) b, CMD_FUNC #undef DEF_X };

甚至,我们可以定义一个命令的字符串,以作打印信息用:

const char* str_cmd[] = { #define DEF_X(a,b) #a, CMD_FUNC #undef DEF_X };

只要这个DEF_X(a,b)里面的a和b是对应关系正确的,CMD_FUNC后面的元素顺序是所谓了,这个比前面的结构体有天然优势。这样,我们就可以直接用下标开始操作了:

void player_cmd_handle(tCmd cmd, void* p) { if(cmd 《 CMD_MAX) { player_funcs[cmd](p); } else { printf(“Command(%d) invalid!\n”, cmd); } }

这不仅提高了效率,还不用担心命令的顺序问题。

这种X-MACRO的用法对分支结构,特别是消息命令的处理特别的方便高效。

以下附上该案例的完整测试源码:

#include 《stdio.h》 #define FUNC_IN() printf(“enter %s \r\n”, __FUNCTION__) #define CMD_FUNC \ DEF_X(CMD_PLAY, func_cmd_play) \ DEF_X(CMD_PAUSE, func_cmd_pause) \ DEF_X(CMD_STOP, func_cmd_stop) \ DEF_X(CMD_PLAY_NEXT, func_cmd_play_next) \ DEF_X(CMD_PLAY_PREV, func_cmd_play_prev) \ typedef enum { #define DEF_X(a,b) a, CMD_FUNC #undef DEF_X CMD_MAX }tCmd; const char* str_cmd[] = { #define DEF_X(a,b) #a, CMD_FUNC #undef DEF_X }; typedef void(*pFunc)(void* p); void func_cmd_play(void* p) { FUNC_IN(); } void func_cmd_pause(void* p) { FUNC_IN(); } void func_cmd_stop(void* p) { FUNC_IN(); } void func_cmd_play_next(void* p) { FUNC_IN(); } void func_cmd_play_prev(void* p) { FUNC_IN(); } const pFunc player_funcs[] = { #define DEF_X(a,b) b, CMD_FUNC #undef DEF_X }; void player_cmd_handle(tCmd cmd, void* p) { if(cmd 《 CMD_MAX) { player_funcs[cmd](p); } else { printf(“Command(%d) invalid!\n”, cmd); } } int main(void) { player_cmd_handle(CMD_PAUSE, (void*)0); player_cmd_handle(100, (void*)0); return 0; }

留个作业题:

如何灵活地将一个结构体的内容系列化到一个数组中,以及如何将一个数组的内容解系列化到结构体中?

例如,将以下结构体s的内容copy到data中(别老想着memcopy哦):

typedef struct STRUCT_DATA { int a; char b; short c; }tStruct;tStruct s; unsigned char data[100];

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5209

    文章

    20656

    浏览量

    337034
  • 宏汇编器
    +关注

    关注

    0

    文章

    7

    浏览量

    9090
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    低成本键盘旋钮开源项目介绍

    办公切软件、调参数太繁琐?创作时控笔刷 / 缩放总找快捷键?商用键盘价格高,还难适配个性化操作需求?想自制键盘,却遇设计复杂、组装难度高的问题?
    的头像 发表于 03-25 11:09 521次阅读

    微科技首家欧洲全资子公司正式成立

    在国家积极鼓励企业“走出去”及共建“一带一路”倡议的宏大背景下,国内功率半导体领域的领先企业——江苏微科技股份有限公司(以下简称“微科技”)今日宣布,其首家欧洲全资子公司 MacMic
    的头像 发表于 03-04 11:06 648次阅读

    变频器的特殊用法

    变频器作为电力电子技术的重要应用设备,其核心功能是通过改变电源频率实现对电机转速的精确控制。然而在实际工业场景和创意应用中,工程师们早已突破传统认知,开发出一系列令人惊叹的特殊用法。这些创新实践不仅拓展了变频器的应用边界,更展现了电力电子技术的无限可能。
    的头像 发表于 03-03 17:08 609次阅读

    龙芯中科与杉科技完成产品适配互认证

    近日,龙芯中科技术股份有限公司与杭州杉科技股份有限公司完成基于龙芯3C6000处理器平台对杉分布式存储系统的适配与性能验证。测试结果表明,杉分布式存储产品在龙芯3C6000平台上运行稳定、性能优异,标志着双方在国产自主计算
    的头像 发表于 01-23 17:08 978次阅读
    龙芯中科与<b class='flag-5'>宏</b>杉科技完成产品适配互认证

    C语言中实现函数的三种方式

    1. 函数介绍 函数,即包含多条语句的定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。 函数本质上为
    发表于 12-29 07:34

    微科技一举斩获两项重磅行业大奖

    聚力创新动能,破局产业难题,领航技术前沿!近期深圳半导体与电源技术领域盛会密集,微科技携旗下子公司上海微爱赛半导体有限公司(以下简称“微爱赛”)强势亮相。凭借突出的技术实力与产品性能,
    的头像 发表于 12-19 17:22 1001次阅读
    <b class='flag-5'>宏</b>微科技一举斩获两项重磅行业大奖

    泰科技与ADI正式签署合作备忘录

    近日,南京泰半导体科技股份有限公司(以下简称“泰科技”)与全球领先的高性能半导体公司ADI正式签署合作备忘录,双方将在半导体测试与精密信号测量领域展开持续深度合作,共同推动高性能测试系统的技术创新与市场应用落地。
    的头像 发表于 12-02 09:09 1681次阅读

    C语言的printf基本用法介绍

    大家只需要掌握最基本的用法,以后随着编程知识的学习,我们会逐步介绍更加高级用法,最终让大家完全掌握 printf。
    发表于 11-12 07:04

    齐光多种不同封装贴片发光管点亮多元应用新视界

    在当今快速发展的科技时代,贴片发光管凭借其体积小、功耗低、亮度高、色彩丰富等优点,广泛应用于各个领域。齐光多种不同封装贴片发光管点亮多元应用新视界,为现代生活和工业生产带来了诸多便利与创新。那么
    的头像 发表于 10-17 16:51 1565次阅读
    <b class='flag-5'>宏</b>齐光多种不同封装贴片发光管点亮多元应用新视界

    干货放送!集物流运输冲击记录仪直播精选问答集锦,你想知道的都在这

    集技术主题直播于8月20日圆满结束,感谢各位热情参与!直播中,集科技高级技术工程师针毕工对不同物流运输的实际应用场景,为大家分析如何高效监测运输环境,保障货物安全交付和厘清货损责任。直播中,我们
    的头像 发表于 09-02 17:03 972次阅读
    干货放送!<b class='flag-5'>宏</b>集物流运输冲击记录仪直播精选问答集锦,你想知道的都在这

    干货放送!集科技物联网技术直播精选问答集锦,建议收藏!

    集直播圆满结束,感谢各位热情参与!直播上,集科技多位高级技术工程师针对不同产线的实际应用场景,为大家分析如何突破瓶颈,提高生产效率。直播中,我们收到了众多提问,展现了大家对集物联
    的头像 发表于 08-08 18:24 674次阅读
    干货放送!<b class='flag-5'>宏</b>集科技物联网技术直播精选问答集锦,建议收藏!

    景智驾再获老股东追加投资

    近日,景智驾再次获得老股东博将资本与衢州智远的追加投资。在竞争激烈、技术迭代加速的市场环境下,此次支持彰显了投资方对景智驾核心能力与长期发展潜力的高度认可。
    的头像 发表于 06-09 11:06 985次阅读

    Vicor助力发打造主动悬架电源系统

    厦门发电声股份有限公司(发)打造业内性能卓越的主动悬架电源系统,旨在将长期以来仅见于豪华车型的功能引入中端车型。发成功突破困扰知名汽车技术供应商几十年的技术瓶颈,在满足主动悬架系统对尺寸、重量及瞬态性能的严苛需求的同时,兼
    的头像 发表于 06-04 15:24 1329次阅读

    微科技2025供应商大会成功举办

    近日,以“协同·精进·共赢”为主题的微科技2025供应商大会在常州隆重召开。来自全国各地的专家和供应商伙伴们齐聚一堂,与微科技共同回顾合作成果,展望未来蓝图。
    的头像 发表于 05-30 15:33 1078次阅读

    harmony OS NEXT-Navagation基本用法

    # Navagation基本用法 > Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏,内容栏和公工具栏,其中内容区默认首页显示导航内容
    的头像 发表于 04-27 17:39 1070次阅读