define、defined、ifdef、ifndef、undef的用法都属于预处理部分,所谓预处理是指在进行第一遍扫描(语法扫描和语法分析)之前所做的工作。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分做处理,处理完毕自动进入对源程序的编译。C语言提供多种预处理,如宏定义、文件包含、条件编译等。
一、 宏定义
a. 带参宏定义中,宏名和形参表之间不能有空格出现。
如把: #define MAX(x,y) (x>y)?x:y 写为: #define MAX (x,y) (x>y)?x:y将被认为是无参宏定义,宏名MAX代表字符串 (x,y) (x>y)?x:y
b. 在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。
如#define SUM(int x, int y) (x+y) 这样使用是错误的。
c. 在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
#define SQ(y) (y)*(y)
int main()
{
int a = 3, sq;
sq = SQ(a+1);
printf(“sq = %d\n”, sq);
return 0;
}
首行中宏定义的y是形参,sq = SQ(a+1);语句中宏调用实参为a+1,是一个表达式;宏展开后得到语句:sq = (a+1)*(a+1),这与函数调用不同,函数调用时要把实参表达式的值的求出来再赋予形参:sq = 4*4;
d. 在宏定义中,字符串内的形参参通常要用括号括起来,以免出错。如上面的c.中把首行改为: #define SQ(y) y*y
结果将是:sq = a+1*a+1, 最终输出结果是7,而不是16。
即使参数两边加括号,还是不够,
如把首行改为:#define SQ(y) (y)*(y)
sq = 160/SQ(a+1);
则实际运算为:sq = 160/(a+1)*(a+1) ,因“/”和“*”优先级和结合性相同,所以sq = (160/(a+1))*(a+1) 最终结果是160, 而不是预期的10。
故而宏定义不仅应该在参数两边加括号,也就在整个字符串外加括号:#define SQ(y) ((y)*(y)) 。
e. 带参的宏和带参函数很相似,但本质上有不同,把同一表达式用函数调用处理和用宏处理,两者结果可能不同。
例A:
int SQ(int y)
{
return ((y)*(y));
}
int main()
{
int i = 1;
while(i<=5)
{
printf(“%d\n”, SQ(i++));
}
return 0;
}
例B:
#define SQ(y) ((y)*(y))
int main()
{
int i = 1;
while(i<=5)
{
printf(“%d\n”, SQ(i++));
}
return 0;
}
例A中,函数调用是把实参i值传给y后自增1,然后输出函数值,因此要循环5次,输出:1,4,9,16,25;而例B中,宏调用时,仅作代换第一次循环时,表达式中前一个i初值是1,然后自增1变成2,表达式中后面的i初值是2,然后i自增1变成3,因3<5,继续第二次循环:3*4;然后是第三次循环:5*6。最终结果是输出:2,12,30。
2.预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
3.宏定义不是说明或语句,不必在行末加分号,如加上分号,则连分号也一起置换。
4.宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束,如要终止作用域,可使用#undef命令,#undef 是删除前面定义的宏名字,它“不定义”宏。如:
# define PI 3.4
int Func1()
{
......
}
#undef PI
int Func2()
{
......
}
PI只在Func1函数中有效,在Func2函数中无效。
5.宏名若在源程序中用引号括起来,则预处理程序不对其作宏代换。
#define OK 100
int main()
{
printf(“OK”); //此处运行结果是打印OK , OK作为字符串被打印。
return 0;
}
6.预宏定义
C规范了5个预宏定义:
__DATE__ 进行预处理的日期(“Mmm dd yyyy” 形式的字符串文字);
__FILE__ 代表当前源代码文件名的字符串文字;
__LINE__ 代表当前源代码的等号的整数常量;
__TIME__ 源文件编译时间,格式为“hh:mm:ss” ;
__func__ 当前所在函数名;
二、使用defined
确定宏M是否定义,可以使用下列两种预处理命令之一:
#ifdef M
或
#if defined M
注意两者都有个define的作用,区别在于使用方式上。前者的通常用法是:
#ifdef XXX
....
#else
....
#endif
只能在两者中选择是否有定义。对于后者,常用法是:
#if defined XXX1
....
#elif defined XXX2
....
#elif defined XXX3
....
#endif
可以在多个中选择是否有定义。
这个#if defined XXX它不管里面的“XXX"的逻辑是“真”还是“假”它只管这个程序的前面的宏定义里面有没有定义“XXX”这个宏,如果定义了x这个宏,那么,编译器会编译中间的…code…否则不直接忽视中间的…code…代码。
另外 #if defined(XXX)也可以取反,也就用 #if !defined(XXX)。
三、#和##
1. “#”预处理操作符,也称字符串化操作符,其作用是把其后的字符串变成用双引号包围的字符串,如:
#define mkstr(s) #s
int main()
{
printf(mkstr(Hello Word!)); //预处理程序将此语句变成:printf(“Hello Word!”);
return 0;
}
2. “##”连接运算符,可以把两个独立的字符串连接成一个字符串,如:
#include SORT(X) sort_function ##X
int main()
{
char *array;
int elements, element_size;
SORT(3)(array, elements, element_size);
return 0;
}
此例中,倒数第三行实际上转换为:sort_function3(array, elements, element_size);
从此宏SORT的用法可以看出,如果在运行时才确定要调用哪个函数,则可用“##”运算符动态地构造要调用的函数的名称。
四、防止头文件重复编译
#ifndef GET_INFOR_H
#define GET_INFOR_H
#include
........
........
#endif
当第一次包含文件get_infor.h时,GET_INFOR_H还未被定义成预处理变量,故#ifndef检查为真,于是预处理器依次处理#ifndef GET_INFOR_H后面的语句,则会执行#define GET_INFOR_H,此时GET_INFOR_H会被定义成预处理变量。故当此文件再次被编译时,#ifndef检查结果变为假,那么此文件将不会再次被编译。
在这整个程序中预处理变量必须是唯一的。虽然其写法可以有很多中,也没有必须的要求,但是考虑到确保预处理变量的唯一性和程序的可读性,通常会基于头文件的名字来定义相应的预处理变量。例如上面的头文件名字为get_infor.h,那么预处理变量则定义为GET_INFOR_H。全部大写也能够较好的确保其唯一性,避免冲突。
另外,头文件即使目前还没有被包含在任何的其他文件中,也应该设置一下头文件保护。这样操作也不会增加程序的复杂度。因此,最好养成随手加头文件保护的好习惯。
巧用#ifndef不仅可以用于头文件保护还可以用来调试程序,或者写程序的时候如果对于某个语句是否执行需要加限制条件,都可以这种方法。因为 #ifdef 和#endif中间的语句是否执行完全取决于你是否加上#define。