【宏定义】
Macro属于编译期,而非运行期
'#'的功能是阻止后面宏的展开,直接将后面的宏参数进行字符串替换(字符串化,stringizing) 如:
#define WARN_IF(EXP) do { \
if (EXP) \
fprintf(stderr, "warning: "#EXP"\n"); \
} while(0)
执行过程中,
WARN_IF(divider == 0)
会被替换成
do {
if (divider == 0)
fprintf(stderr, "warning: ""divider == 0""\n");
} while(0)
'##'的作用是字符串连接
具体的区别如下代码所示:
#include <stdio.h>
#define SHOW_VALUE(instance, member) do { \
printf("%s\'s %s is %s\n", #instance, #member, instance.member); \
} while(0)
#define SHOW_VALUE_ANOTHER(which) do { \
printf("%s\n", which##_name); \
} while(0)
int main(int argc, char *argv[])
{
struct person {
char first_name[20];
char last_name[20];
};
struct person a = {"Kobe", "Brant"};
char *first_name = "Tom";
char *last_name = "Jerry";
SHOW_VALUE(a, first_name);
SHOW_VALUE(a, last_name);
SHOW_VALUE_ANOTHER(first);
SHOW_VALUE_ANOTHER(last);
return 0;
}
输出为:
a's first_name is Kobe
a's last_name is Brant
Tom
Jerry
【可变参数宏】
宏可以接收可变数据的参数,如:
#define LOG(format, ...) printf(format, __VA_ARGS__)
其中'...'表示参数可变,VA_ARGS 在预处理中由实际的参数集所替换
假设稍后调用该宏:
LOG("%s\n", "Howdy");
LOG("weight = %d, shipping = $%.2f\n", 1, 2.0);
对于第二次调用,VA_ARGS 展开为2个参数:wt和sp。 展开后的代码为:
printf("Howdy");
printf("weight = %d, shipping = $%.2f\n", 1, 2.0");
在GCC中,同时支持如下的形式:
#define LOG(format, args...) printf(format, args)
用法与上面的一致。
但上面这种写法会有个问题:可变参数必须存在。若没有可变参数,编译就会报错。 这里可以使用"##"连接符,用以对toke进行连接,因此做如下修改:
#define LOG(format, args...) printf(format, ##args);
在调试环境下,通常会定义一个打印调试信息的宏,如:
#define __ENABLE_DBG__
#ifdef __ENABLE_DBG__
#define DBG_PRINTF(dbg_level, fmt, args...) do { \
if (dbg_level <= get_debug_level()) \
printf("\t [module] %s(%d). " fmt "\n", __FUNCTION__, __LINE__, ##args); \
} while (0)
#else
#define DBG_PRINTF(lvl, fmt, args...)
#endif
【可变参数: stdarg.h】 前面提到了变参宏,可以接收可变数量的参数。stdarg.h头文件为函数提供了一个类似的功能。 其用法较为复杂,必须按照如下步骤进行:
1. 提供函数原型;
2. 在函数定义中创建一个va_list类型的变量;
3. 用宏把该变量初始化为一个参数列表;
4. 用宏访问参数列表;
5. 用宏完成清理工作。
-
函数原型的形参列表中,至少有一个形参和一个省略号。其中,最右边的形参(省略号左边第一个) 表示省略号部分的参数数量(使用parmN来代表该形参)
-
接下来定义一个va_list类型的变量,
va_list ap;
用于储存形参列表中省略号部分的数据对象(这里只是声明,还未存放)
-
将参数列表拷贝到va_list类型的变量中
va_start(ap, parmN);
该宏有两个参数,分别为va_list类型的变量和parmN形参
- 下一步是访问参数列表的内容,使用宏va_arg()。该宏接收两个参数:一个va_list类型变量和一个类型名。 类型名指定了返回值的类型。第一次调用va_arg()时,它返回参数列表的第一项;第二次调用va_arg()时,它返回参数列表的第二项,以此类推。
注意:传入的参数类型必须与宏参数的类型相匹配。
- 最后,要使用va_end()宏完成清理工作。如,释放动态分配用于存储参数的内存。 该宏接收一个va_list类型的变量。 在调用va_end()后,只有重新调用va_start初始化ap后,才能使用变量ap。