宏的高级用法

对于条件/分支处理的程序设计,我们惯性地会选择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];

婴儿洗衣机方案开发
AMD高端显卡Radeon RX 490本月登场?
智慧交通的应用现状是怎样的
ATX电源主电源的修复过程
ate系统是什么,ate自动电源测试系统的介绍
宏的高级用法
意法半导体STNRG012实现精确的软开关操作
物通博联数据采集网关支持电流电压模拟量数字量485数据采集
两个接近开关如何进行正反转和转速检测
传真机的打印暗盒
华为nova3和荣耀10对比分析,看你喜欢哪一款?
你准备今天预定小米6嘛?价格其实是降了,京东超过110万人预
复旦微电子团队实现多桥沟道晶体管技术
超五类屏蔽双绞线搭配什么类型的水晶头
与高通和解对苹果自主研发基带芯片有何影响?
智能制造的核心是什么?
OPPOR11什么时候上市?OPPOR11最新消息:光学变焦+背景虚化,OPPOR11真正的人像摄影大师
信号线用共模扼流圈的偏移改善功能
英特尔升级芯片是真是假 它的目的是什么
绿联MFi认证苹果数据线拆解评测