如何使用C语言可变参数的宏

最近带了个新徒弟,这家伙读代码的时候看到宏定义“…”就懵圈了。遂写了几个例子说明了一下。
我都没想到自己还能写出有点意义的,并且公开出来还不违反保密协议的东西。
所以粘出来分享一下。作为一个老司机,我好像第一次写C语言的东西。还真是不务正业啊。

首先说明,凡是能用带参宏的,都可以用函数。带参数的宏这玩意儿根本是C程序员偷懒用的,宏写得越复杂,代码可读性越低。所以你看完后如果觉得没什么用,那是再正常不过了。

    例一:

封装printf函数,在前面加上函数名和行号。

#define PRINT_MORE(...) {\
                        printf("FUN[%s]\tLINE[%d]\t",__FUNCTION__,__LINE__);\
                        printf(__VA_ARGS__); }

这里可变参被直接传给了printf。唯一的要点就是…是形参,向下传的时候要变成__VA_ARGS__。
…是C99的标准,arg…是GNU的标准,GNU兼容C99,所以何必给自己找麻烦呢?

    例二:

封装printf函数,在前面加上函数名和行号,后面加上作者。

#define PRINT_WITH_AUTHOR(author,...) {\
                        printf("FUN[%s]\tLINE[%d]\t",__FUNCTION__,__LINE__);\
                        printf(##__VA_ARGS__);\
                        printf("\tAUTHOR[%s]\n",author); }

这个例子是为了说明固定参跟可变参一起用的情况,当然是固定参在前面了。另外__VA_ARGS__的前面加上了##。但是在这里加不加##都是一样的。跟例一一样,…的部分必须符合printf的格式要求,不传编译不过,格式不对运行时出错。并不是真正的“可变”。关于那个##__VA_ARGS__,后面再说。

    例三:

打印一个数。第二个参数给‘h’或者不给第二个参,按照十六进制打印;第二个参给‘h’以外的值,按十进制打印。

#define PRINT_NUM_R(num,mode,...){\
                    (mode == 'h' || mode == 'H') ? printf("num = 0x%08X\n", num) : printf("num = %d\n", num);\
                }
#define PRINT_NUM(num, ...){\
                    PRINT_NUM_R(num, ##__VA_ARGS__, 'h');\
                }

这是类似于C++里参数带默认值的一种实现方法。对付“…”,需要分开使用其中的参数时,我们只需要把宏拆成n份就行了。然后再在不同的层级,向下传递 ##__VA_ARGS__的时候,再后面增加默认值即可。
这个例子里,PRINT_NUM的“…”中如果有内容,那么这部分内容再次向下展开时,__VA_ARGS__的第一个参就会作为PRINT_NUM_R的第二个参传入;如果没有内容,‘h’就会自动增补成第二个参。从而达到设定默认值的作用。
__VA_ARGS__就是“保持传进来的参数的样子”。所以如果“…”的部分什么都没有,那么它也就什么都没有。但是你在第一个参的后面跟了个逗号啊,要是传到第二层不就直接编译错误了吗?写成##__VA_ARGS__,为空的时候可以吃掉它前面的那个逗号。这是##的一个特殊用法,记着就行了。宏里的#和##原本的意义,不在本次讨论之列。
哦对了,这仅仅是个简单的例子。写宏及调用宏的时候最好最好不要用三目运算符(?),否则遇到复杂的情况分号问题会把你搞得欲仙欲死。宏最稳妥的办法是用do(){}while(0)把逻辑框起来。

    例四:

打印行号。如果传参‘h’则按十六进制打印,否则按十进制打印。

#define PRINT_LINE_R(useless,mode,...){\
                (mode == 'h' || mode == 'H') ? printf("line = 0x%08X\n", __LINE__) : printf("line = %d\n", __LINE__);\
            }
#define PRINT_LINE(...){\
                PRINT_LINE_R(0,##__VA_ARGS__,'d');\
            }

跟上面的例子一样,一个宏解决不了的问题,就用两个。
PRINT_LINE_R的第一个参实际是没用的。它存在的意义是,##__VA_ARGS__在参数为空时只能删掉前面的那个空格,但是删不掉后面的那个啊!你给下面传个“,’d’”算咋回事?
所以第一个参就是用来对付编译器的。##__VA_ARGS__允许为空的时候,不要把它作为第一个参向下展开。

这就是带…的宏的基本用法了,希望有所帮助。

已有7条评论

  1. “请不要留下无趣的东西浪费大家的时间”

  2. 感觉我已经看不懂代码了,今天改EM支持PHP7的时候,已经完全懵逼了。

    1. 对于文科生来说已经很不错了。

  3. C语言,久违了,毕业后就没碰过了~

  4. 封面图是angularJS?你自己拍的吗。

    1. 向毛主席保证,本站特色图片无一亲自拍摄。
      是个编辑器就能调整成图片的效果吧。

你好,新朋友。留言前请先填写昵称邮箱