由预处理器定义的符号如下:
符号 | 样例值 | 含义 |
---|---|---|
__FILE__ |
“name.c” | 进行编译的源文件名 |
__ LINE__ |
25 | 文件当前的行号 |
__DATE__ |
“Jan 31 1997” | 文件被编译的日期 |
__TIME__ |
“18:04:30” | 文件被编译的时间 |
__STDC__ |
1 | 如果编译器遵循ANSI C ,其值就为1,否则未定义 |
你在这之前可能已经见过#define的一些简单用法,就是为数值命名一个符号。
下面,为你介绍它的更多用途。
#define name stuff
有了这个指令后,每当有符号name出现在这条指令后面,预处理器就会把它替换为stuff。
K&R C
早期的C 编译器要求#出现在每行的起始位置,不过它的后面可以跟一些空白。在ANSI C 中,这条限制被取消了。
替换文本并不仅限于数值字面值常量。使用#define
指令,你可以把任何文本替换到程序中。
如:
#define reg register
#define do_forever for(;;)
#define CASE break;case
第一个只是为register创建一个简单的别名。第二个用更具有描述性的符号来代替一种用于实现无限循环的for语句。最后一个,用于switch语句中。
如果定义中的stuff非常长,可以用\
将它分为几行。
如下:
#define DEBUG_PRINT printf("File %s line %d:"\
"x = %d,y = %d,z = %d,"\
__FILE__,__LINE__,\
x,y,z)
我们可以这程序中这样使用它:
x *= 2;
y += x;
z = x*y;
DEBUG_PRINT;
注意:
DEBUG_PRINT后面加了一个分号,所以你不应该在宏定义的尾部加上分号。
否则会产生两条语句。在if else 的情况下容易出错(没有{ }
)
#define 机制包括一个规定,允许把参数替换带文本中,这种实现通常称为宏(macro)或定义宏(defined macro)。下面是其声明方式:
#define name(parameter-list) stuff
其中,parameter_list (参数列表)是一个由逗号分隔的符号列表,它们可能出现在stuff中。参数列表的左括号必须与name紧邻(不允许出现空格)。
当宏被调用时,名字后面是一个由逗号分隔的值的列表,每个值都与宏定义中的一个参数相对应,整个列表用一对括号包围。当参数出现在程序中时,与每个参数对应的实际值都将被替换到stuff中。
#define SQUARE(x) x*x
这样,你在程序中使用SQUARE(5),它将被替换为5*5。
注意:
当调用SQUARE(a+1)时,将出现问题,它被替换为 a+1*a+1 结果为2*a+1;
正确的做法是加上两个括号
#define SQUARE(x) (x)*(x)
但是,还有问题。如下:
#define DOUBLE(x) (x)+(x)
a = 5;
printf("%d\n",10*DOUBLE(a));
10*DOUBLE(a) --> 10*(5) + (5) 由于*具有更高的优先级,我们仍然得不到我们所认为的结果。
我们应该在加上一对括号
((x)+(x))
通过这些栗子,我们可以看出预处理器做的事情十分的简单粗暴。
当程序中扩展#define 定义符号和宏时,需要涉及几个步骤。
宏参数和#define定义可以包含其他#define定义的符号。但是,宏不可以出现递归。
当预处理器搜索#define定义的符号时,字符串常量的内容不进行检查。你如果想把宏参数插入到字符串常量中,可以使用两种技巧。首先,领近字符串自动连接的特性使我们很容易把一个字符串分成几段,每段实际上都是一个宏参数。
#define PRINT(FORMAT,VALUE)
printf("The value is " FORMAT "\n",VALUE)
...
PRINT("%d",x+3);
这种技巧只有当字符串常量作为宏参数给出时才能使用。
第二个技巧使用预处理器将一个宏参数转换为一个字符串。#argument 这种结构被预处理器翻译为"argument"。这种翻译可以让你这些编写代码:
#define ADD_TO_SUM(sum_number,value) \
sum ## sum_number += value
...
ADD_TO_SUM(5,25);
这条语句将25加到变量sum5。这种连接必须产生一个合法的标识符。
宏非常频繁地用于执行简单的计算,比如比较两个表达式中较大的一个。
#define MAX(a,b) ((a)>(b) ? (a):(b))
为什么不用函数呢?
#define MALLOC(n,tupe) \
( (type *)malloc( (n)*sizeof(type) ) )
函数无法使用类型作为参数,但是宏可以。
pi = MALLOC(25,int);
当宏参数在宏定义中出现的次数超过一定次数时,如果这个参数具有副作用,那么当你使用这个宏时就可能出现危险。
比如我们 用 x+1 和 x++;
后者便会产生符作用。
#define MAX(a,b) ( (a)>(b) ? (a):(b) )
...
x = 5;
y = 8;
z = MAX(x++,y++);
printf("x=%d,y=%d,z=%d\n",x,y,z);
这个程序打印的结果为 x=6,y=10,z=9。
z = ( (x++)>(y++) ? (x++):(y++) );
为什么那个加大的值增值了两次?注意后面的 (x++)。
类似于这些的有副作用需要小心使用。
为了和函数区分开:命名约定十分重要。
一种常见的约定便是使用全部大写。
下面这条预处理指令用于移除一个宏定义。
#undef name
如果一个现存的名字需要被重新定义,那么它的旧定义首先必须用#undef移除。
许多C编译器提供了允许你在命令行中定义符号用于启动编译过程。
在UNIX编译器中,-D选项可以完成这些任务。
-Dname //name 值默认为1
-Dname=stuff //name = stuff
cc -DARRAY_SIZE=100 prog.c
条件编译(conditional compilation):
选择某条语句或者某组语句进行翻译或者被忽略。
#if constant-expression
statements
#endif
其中constant-expression(常量表达式)由预处理器进行求值。如果值为非零值,那么statements部分就被正常编译,否则就忽略它们。
#if DEBUG
printf(" x=%d , y=%d\n",x,y);
#endif
不管我们是想编译(1)还是忽略它(0),使用
#define DEBUG 1
我们可以将这个与命令行定义组合起来使用,通过-DDEBUG=0 , -DDEBUG=0 来选择(在IDE中就没有必要了)
#if指令还有可选的#elif 和 #else
#if cosntant-expression
statements
#elif constant-expression
other statements...
#else
other statements
#endif
测试一个符号是否已被定义是非常有必要的。
我们可以通过以下这些方式进行:
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
每对定义的两条语句都是等价的,但#if形式功能更加强。因为常量表达式可能包含额外的条件,如:
#if X>0 || defined( ABC ) && defined( BCD )
嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_of_option1();
#endif /*OS_UNIX*/
#ifdef OPTION2
unix_version_of_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_of_option2();
#endif /*OS_MSDOS*/
#endif
文件包含分为:函数库文件包含和本地文件包含(实际区别很小)
#include
#include "game.h"
<>比如UNIX , 在/user/include(标准位置)目录查找函数库头文件。
""的一种策略就是先在源文件的当前目录查找,然后像查找库文件一样在标准位置查找
为了避免包含同一个头文件多次,我们使用
#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1
/*
..
..
..
*/
#endif