(port)) = ((int)(val)))
? ? ?18. 使用一些宏跟踪调试:
?
? ? ?ANSI标准说明了五个预定义的宏名(注意双下划线),即:__LINE__、__FILE __、__DATE__、__TIME__、__STDC __。
?
? ? ?若编译器未遵循ANSI标准,则可能仅支持以上宏名中的几个,或根本不支持。此外,编译程序可能还提供其它预定义的宏名(如__FUCTION__)。
?
? ? ?__DATE__宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期;源代码翻译到目标代码的时间作为串包含在__TIME__中。串形式为时:分:秒。
?
? ? ?如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。
?
? ? ?可以借助上面的宏来定义调试宏,输出数据信息和所在文件所在行。如下所示:
?
1 #define MSG(msg, date) ? ? ?printf(msg);printf(“[%d][%d][%s]”,date,__LINE__,__FILE__)
? ? ?19. 用do{…}while(0)语句包含多语句防止错误:
?
1 #define DO(a, b) do{\
2 ? ? a+b;\
3 ? ? a++;\
4 }while(0)
? ? ?20. 实现类似“重载”功能
?
? ? ?
C语言中没有swap函数,而且不支持重载,也没有模板概念,所以对于每种数据类型都要写出相应的swap函数,如:
?
1 IntSwap(int *, ?int *); ?
2 LongSwap(long *, ?long *); ?
3 StringSwap(char *, ?char *);?
? ? ?可采用宏定义SWAP(t,x,y)以交换t类型的两个参数(要使用程序块结构):
?
复制代码
?1 #define SWAP(t, x, y) do{\
?2 ? ? t temp = *y;\
?3 ? ? *y = *x;\
?4 ? ? *x = temp;\
?5 }while(0)
?6?
?7 int main(void){
?8 ? ? int a = 10, b = 5;
?9 ? ? SWAP(int, &a, &b);
10 ? ? printf("a=%d, b=%d\n", a, b);
11 ? ? return 0;
12 }
复制代码
? ? ?21. 1年中有多少秒(忽略闰年问题) :
?
1 #define SECONDS_PER_YEAR ? ?(60UL * 60 * 24 * 365)
? ? ?该表达式将使一个16位机的整型数溢出,因此用长整型符号L告诉编译器该常数为长整型数。
?
? ? ?注意,不可定义为#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL,否则将产生(31536000)UL而非31536000UL,这会导致编译报错。
?
? ? ?以下几种写法也正确:
?
1 #define SECONDS_PER_YEAR ? ?60 * 60 * 24 * 365UL
2 #define SECONDS_PER_YEAR ? ?(60UL * 60UL * 24UL * 365UL)
3 #define SECONDS_PER_YEAR ? ?((unsigned long)(60 * 60 * 24 * 365))
? ? ?22. 取消宏定义:
?
? ? ? ? ? #define [MacroName] [MacroValue] ? ? ? //定义宏
?
? ? ? ? ? #undef [MacroName] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //取消宏
?
? ? ?宏定义必须写在函数外,其作用域为宏定义起到源程序结束。如要终止其作用域可使用#undef命令:
?
复制代码
1 #define PI ? 3.14159
2 int main(void){
3 ? ? //……
4 }
5 #undef PI
6 int func(void){
7 ? ? //……
8 }
复制代码
? ? ?表示PI只在main函数中有效,在func1中无效。
?
2.3.2 特殊用法
? ? ?主要涉及
C语言宏里#和##的用法,以及可变参数宏。
?
2.3.2.1 字符串化操作符#
? ? ?在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。#只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。例如:
?
1 #define EXAMPLE(instr) ? ? ?printf("The input string is:\t%s\n", #instr)
2 #define EXAMPLE1(instr) ? ? #instr
? ? ?当使用该宏定义时,example(abc)在编译时将会展开成printf("the input string is:\t%s\n","abc");string str=example1(abc)将会展成string str="abc"。
?
? ? ?又如下面代码中的宏:
?
1 define WARN_IF(exp) do{ \
2 ? ? if(exp) \
3 ? ? ? ? fprintf(stderr, "Warning: " #exp"\n"); \
4 }while(0)
? ? ?则代码WARN_IF (divider == 0)会被替换为:
?
1 do{
2 ? ? if(divider == 0)
3 ? ? ? ? fprintf(stderr, "Warning" "divider == 0" "\n");
4 }while(0)
? ? ?这样,每次divider(除数)为0时便会在标准错误流上输出一个提示信息。
?
? ? ?注意#宏对空格的处理:
?
忽略传入参数名前面和后面的空格。如str= example1( ? abc )会被扩展成 str="abc"。
当传入参数名间存在空格时,编译器会自动连接各个子字符串,每个子字符串间只以一个空格连接。如str= example1( abc ? ?def)会被扩展成 str="abc def"。
2.3.2.2 符号连接操作符##
? ? ?##称为连接符(concatenator或token-pasting),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量。例如:
?
1 #define PASTER(n) ? ? printf( "token" #n " = %d", token##n)
2 int token9 = 9;
? ? ?则运行PASTER(9)后输出结果为token9 = 9。
?
? ? ?又如要做一个菜单项命令名和函数指针组成的结构体数组,并希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:
?
1 struct command{
2 ? ? char * name;
3 ? ? void (*function)(void);
4 };
5 #define COMMAND(