1.预定义符号
下表总结由预处理器定义的符号。他们的值或者是字符串常量,或者是十进制数字常量。__FILE__和__LINE__在确认调试输出的来源方面很有用处。__DATE__和__TIME__常用语在被编译函数中加入版本信息。__STDC__用于那些在ANSI环境和非ANSI环境必须进行编译的程序中结合条件编译。
符号 | 样例值 | 含义 |
---|---|---|
__FILE__ | "name.c" | 进行编译的源文件名 |
__LINE__ | 25 | 文件当前行的行号 |
__DATE__ | "JAN 31 1997" | 文件被编译的日期 |
__TIME__ | "16:01:22" | 文件被编译的时间 |
__STDC__ | 1 | 如果编译器遵循ANSIC,其值就为1,否则为定义 |
2.#define
#define name stuff
#define REG register
#define DO_FOREVER for(;;)
#define CASE break;case
#define DEBUG_PRINT printf("File %s line %d:" \
"x=%d, y=%d, z=%d",\
__FILE__, __LINE__, \
x, y, z )
2.1宏
#define机制包括了一个规定,运行把参数替换到文本中,这种实现通常称为宏(macro)或定义宏
#define name( parameter-list ) stuff
注意一下的差异:
#define SQUARE(x) x*x
#define SQUARE(x) (x)*(x)
#define DOUBLE(x) (x)+(x)
#define DOUBLE(x) ((x)+(x))
所有对于数值表达式进行求值的宏定义都应该用这种方式加深括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。
2.2#define替换
当预处理器搜索#define定义的符号时,字符串常量的内容并不进行检查。如果想把宏参数插入到字符串常量中,可以使用两种技巧。首先,邻近字符串自动连接的特性可以把一个字符串分成几段,每段实际上都是一个宏参数。如:
#define PRINT1( FORMAT, VALUE) printf("The value is "FORMAT"\n" , VALUE)
PRINT1("%d", x + 3);
输出结果为:
The value is 3
第2个技巧使用预处理器把一个宏参数转换成字符串。#argument这种结构被预处理器翻译为"argument"。如下:
#define PRINT1( FORMAT, VALUE) printf("The value of "#VALUE" is "FORMAT"\n" , VALUE)
PRINT1("%d", x + 3);
输出结果为:
The value of x + 3 is 3
##结构则执行一种不同的任务。它把位于它两边的符号连成一个符号。它允许宏定义从分离的文本片段中创建标识符。下面这段代码使用这种连接把一个值添加到几个变量之一:
#define ADD_TO_SUM( sum_number, value) \
sum ## sum_number += value
...
ADD_TO_SUM( 5, 25 );
最后一条语句把值25添加到sum5变量中。注意这种连接必须产生一个合法的标识符。
2.3宏与函数
属性 | #define宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都将被插入程序中。除了非常小的 宏之外,程序的长度将大幅度增加 |
函数代码只出现于一个地方;每次调用这个函数时, 都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数调用/返回的额外开销 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除 非它们加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果。 |
函数的参数只在函数调用时求值一次,它的结果值传 递给函数。表达式的求值结果更易预测。 |
参数求值 | 参数每次用于宏定义时,它们都将重新求值。由于多 次求值,具有副作用的参数可能会产生不可预料的结果 |
参数在函数被调用前只求值一次。在函数中多次使 用参数并不会导致多种求值过程。参数的副作用并 不会造成任何特殊的问题。 |
参数类型 | 宏和类型无关。只要对参数的操作是合法的,它可以使 用于任何参数类型。 |
函数的参数是与类型有关的。如果参数的类型不同, 就需要使用不同的函数,即使它们执行的任务是相 同的 |
宏较函数的不利之处在于每次使用宏时,一份宏定义代码的拷贝都将插入到程序中。除非宏特别短,否则使用宏可能会大幅度增加程序长度。
宏较函数的优点有:
#define MAX( x, y) ( (a)>(b) ? (a) : (b))
1.用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更多,即使用宏比使用函数在程序的规模和速度方面都更胜一筹。
2.函数的参数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用。而上面代码的宏可以对int、long、float、double以及其他任何可以使用>操作符比较值大小的类型。即宏与类型无关。
#define MALLOC( n, type ) ((type *)malloc((n) * sizeof( type )))
pi = MALLOC( 5, int );
pi = (int *)malloc( 5 * sizeof( int ));
3.一些任务无法通过函数实现。上述代码第1行的宏的第2个参数是一种类型,无法作为函数参数进行传递。第2行代码预处理后与第3含代码没有区别。
2.4.宏的副作用
如果宏参数在宏定义中出现次数超过一次,倘若该参数具有副作用(如调用时参数是x++或者getchar()等),那么使用宏可能导致不可预料的结果。
2.5命名约定
常见约定为宏名字全部大写。
2.6#undef
#undef name
这条预处理指令用于移除一个宏定义。如果一个现存的名字需要重新被定义,那么它的旧定义首先必须使用#undef擦除。
3.条件编译
#if constant-expression
statements
#elif constant-expression
other statements
#elif constant-expression
other statements
...
#endif
其中,常量表达式(constant-expression)由预处理器进行求值,如果值为非0,那么statements被正常编译,否则预处理器就忽视它们。
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
对应的两条语句等价,但#if形式功能更强,因为常量表达式可能包含额外的条件。
#if defined( OS_UNIX )
#ifdef OPTION1
unix_version_of_option1();
#endif
#ifdef OPTION2
unix_version_of_option2();
#endif
#elif define( OS_MSDOS )
#ifdef OPTION2
msdos_version_of_option2();
#endif
#endif
#ifndf _HEADERNAME_H
#define _HEADERNAME_H
#endif