C预处理器在程序编译之前查看程序(故称之为预处理器)。根据程序是的预处理指令,预处理器把符号缩写替换成其表示的内容。预处理器可以包含程序所需的其他文件,可以选择让编译器查看哪些代码。

预处理指令以#开头,到后面的第一个换行符为止。也就是说,指令的长度仅限于一个逻辑行(预处理前,编译器会将多行物理行处理为一个逻辑行)。

编译器在预处理前的翻译处理:

I 编译器把源代码中出现的字符映射到源字符集,包括处理多字节字符和三字符序列。

II 代码物理行换行的处理,编译器定位每个反斜杆后面跟着换行符(按下Enter键在源代码文件中换行所生成的字符,而不是指符号表征\n)的实例,并删除它们,把多个物理行转换成一个逻辑行。因为预处理表达式的长度必须是一个逻辑行(一个逻辑行可以是多个物理行)。

III 编译器把文本划分成预处理记号(token,由空格、制表符或换行符分隔的项)序列、空白序列(一个空格替换所有空白序列,但不包括换行符)和注释序列(用一个空格替换)。

以上三步后,开始进行预处理阶段。

#define一般用来定义宏,做宏替换或头文件的保护性定义,分三类:

c语言中宏定义语句define的作用(深入理解库中随处可见的宏)(1)

宏的名称不允许有空格,遵循C变量的命名规则。

替换体也叫替换列表,一旦预处理器在程序中找到宏的实例后,会用替换体代替该宏。

从宏变成最终替换文本的过程称为宏展开(macro expansion)。

#define PRINTX printf("x is %d\n",x)可以理解为:

define a macro, named PRINTX, replaced by printf("x is %d\n",x)

1 宏定义符号常量

#define指令可以用来定义明示常量(manifest constant,也叫符号常量)

下面是使用宏来定义符号常量的实例:

/* simple preprocessor examples */ #include <stdio.h> #define TWO 2 /* you can use comments if you like */ #define OW "Consistency is the last refuge of the unimagina\ tive. - Oscar Wilde" /* a backslash continues a definition to the next line */ #define FOUR TWO*TWO #define PRINTX printf("X is %d.\n", x) #define FMT "X is %d.\n" int main(void) { int x = TWO; /* int x = 2; */ PRINTX; /* printf("x is %d\n",x); */ x = FOUR; /* X = TWO*TWO → X = 2*2 只做替换,包括循环替换,不做计算 */ printf(FMT, x); /* printf("X is %d.\n", x); */ printf("%s\n", OW); /* printf("%s\n", 2); */ printf("TWO: OW\n");/* 双引号中的字符,即使有定义宏,也不做替换,#宏参数例外*/ getchar(); return 0; } /* X is 2. X is 4. Consistency is the last refuge of the unimaginative. - Oscar Wilde TWO: OW */

对于常量(或字面量)替换,也可以使用const定义。但用作静态数组大小时,一些编译器并不支持const定义的常量。

那么,何时使用字符常量?对于绝大部分数字常量,应该使用字符常量。如果在算式中使用字符常量代替数字,常量名能更清楚地表达该数字的含义。如果是表示数组大小的数字,用符号常量后更容易改变数组的大小和循环次数。如果数字是系统代码(如EOF),用符号常量表示的代码更容易移植(只需要改变EOF的定义)。助记、易更改、可移植,这些都是符号常量很有价值的特性。

2 函数宏(带参数的宏)

函数宏(带参数的宏)的定义和使用看起来都像函数,都使用圆括号,但是两者却完全不同,特别是宏参数和函数参数不完全相同(宏参数替换不做计算,不求值,只替换字符序列),使用函数宏还可能会有一些陷阱。

/* macros with arguments */ #include <stdio.h> #define SQUARE(X) X*X #define PR(X) printf("The result is %d.\n", X) int main(void) { int x = 5; int z; printf("x = %d\n", x); z = SQUARE(x); printf("Evaluating SQUARE(x): "); PR(z); z = SQUARE(2); printf("Evaluating SQUARE(2): "); PR(z); printf("Evaluating SQUARE(x 2): "); PR(SQUARE(x 2)); printf("Evaluating 100/SQUARE(2): "); PR(100/SQUARE(2)); printf("x is %d.\n", x); printf("Evaluating SQUARE( x): "); PR(SQUARE( x)); printf("After incrementing, x is %x.\n", x); getchar(); return 0; } /* x = 5 Evaluating SQUARE(x): The result is 25. Evaluating SQUARE(2): The result is 4. Evaluating SQUARE(x 2): The result is 17. Evaluating 100/SQUARE(2): The result is 100. x is 5. Evaluating SQUARE( x): The result is 49. After incrementing, x is 7. */

SQUARE(x 2)展开后:

SQUARE(x 2*x 2),

因为运算符的优先级不一样,所以计算的结果不同于预期。如果是函数参数,会先计算x 2,就不存在优先级的区别。是否有改善的办法呢?我们知道,圆括号可以有最高的优先级,所以需要用足够多的圆括号来确保最高优先级(就像预先做了求值一样),单个参数使用圆括号,整体也使用圆括号:

#define SQUARE(X) ((X)*(X))

但对于SQUARE( x),展开后是:

x* x

x经过了两次自增,并不是期望的一次自增。所以,一般尽量不要做宏中使用自增运算符。

3 用宏参数创建字符串:#运算符

在第一个实例中说到双引号中的字符,即使有定义宏,也不做替换,宏参数例外,C允许在字符串中包含宏参数,但宏参数需以字符#开头,就像特殊的转义字符一样。

看下面的实例:

/* substitute in string */ #include <stdio.h> #define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x))) int main(void) { int y = 5; PSQR(y); PSQR(2 4); getchar(); return 0; } /* The square of y is 25. The square of 2 4 is 36. */

3 用宏参数连结字符串:##运算符

与#运算符类似,##运算符可用于类函数宏的替换部分,能够连结字符串,组成一个新的标识符:

// use the ## operator #include <stdio.h> #define XNAME(n) x ## n #define PRINT_XN(n) printf("x" #n " = %d\n", x ## n); int main(void) { int XNAME(1) = 14; // becomes int x1 = 14; int XNAME(2) = 20; // becomes int x2 = 20; int x3 = 30; PRINT_XN(1); // becomes printf("x1 = %d\n", x1); PRINT_XN(2); // becomes printf("x2 = %d\n", x2); PRINT_XN(3); // becomes printf("x3 = %d\n", x3); getchar(); return 0; } /* x1 = 14 x2 = 20 x3 = 30 */

4 变参宏:...和__VA_ARGS__

stdarg.h提供的可变参数使用的就是宏定义:

typedef char * va_list; #define _INTsizeof(n) ( (sizeof(n) sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)&v _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap = _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 )

C通过把宏参数列表中最后的参数写成...来实现这一功能,这样,预定义宏可用在替换部分中,表明省略号代表什么。

// variadic.c -- variadic macros #include <stdio.h> #include <math.h> #define PR(X, ...) printf("Message " #X ": " __VA_ARGS__) int main(void) { double x = 48; double y; y = sqrt(x); PR(1, "x = %g\n", x); PR(2, "x = %.2f, y = %.4f\n", x, y); getchar(); return 0; } /* Message 1: x = 48 Message 2: x = 48.00, y = 6.9282 */

5 宏和函数的选择

有些编程任务既可以用带参数的宏完成,也可以用函数完成。各自有适应的一些场合。

使用宏比使用普通函数复杂一些,稍有不慎会产生奇怪的副作用。

宏和函数的选择实际上是时间和空间的权衡。宏展开产生内联代码,略占空间,但没有普通函数的跳转,时间较省。特别是用在嵌套循环体内时。当然,在C 中,有一种更优的替换方案,就是使用inline内联函数。

C作为一种强类型语言,普通函数需要明确数据类型,而宏不存在这一限制,不用担心变量类型,因为其本身就只是做字符替换,并不做变量类型检查,当然这也是宏的另一种缺陷。

对于一些简单的函数,程序员通常使用宏:

#define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) #define ABS(X) ((X) < 0 ? -(X) : (X)) #define ISSIGN(X) ((X) == ' ' || (X) == '-' ? 1 : 0)

6 取消宏定义

#undef指令可以取消宏定义,如:

#define LIMIT 444 #undef LIMIT

7 头文件的保护性定义

#ifndef指令判断后面的标识符是否未定义,可以防止相同的宏被重复定义。但更常用来防止多次(重复)包含一个文件,也就是保护性定义:

#ifndef _HEADERNAME_H_ #define _HEADERNAME_H_ ... // 头文件内容 #endif

8 预定义宏

C编译器有一些预定义宏和标识符:

// predef.c -- predefined identifiers #include <stdio.h> void why_me(); int main() { printf("The file is %s.\n", __FILE__); printf("The date is %s.\n", __DATE__); printf("The time is %s.\n", __TIME__); printf("The version is %ld.\n", __STDC_VERSION__); printf("This is line %d.\n", __LINE__); printf("This function is %s\n", __func__); why_me(); getchar(); return 0; } void why_me() { printf("This function is %s\n", __func__); printf("This is line %d.\n", __LINE__); } /* This is line 11. This function is main This function is why_me This is line 21. */

9 宏的经典应用

在MFC的消息映射,就是宏的一个经典应用:

#define DECLARE_MESSAGE_MAP() \ private: \ static const AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA const AFX_MSGMAP messageMap; \ static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \ #define BEGIN_MESSAGE_MAP(theClass, baseClass) / const AFX_MSGMAP* theClass::GetMessageMap() const / { return &theClass::messageMap; } / AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = / { &baseClass::messageMap, &theClass::_messageEntries[0] }; / AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = / { / #define END_MESSAGE_MAP() / {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } / }; /

宏的定义一般放到头文件中,如一些库中就通常包含有宏定义。

宏替换表面看起来有很多缺陷,但在一些库中却用得很普通,如果不是很熟悉的话,一些源代码还真是看得云里雾里的。

-End-

,