C预处理器在程序编译之前查看程序(故称之为预处理器)。根据程序是的预处理指令,预处理器把符号缩写替换成其表示的内容。预处理器可以包含程序所需的其他文件,可以选择让编译器查看哪些代码。
预处理指令以#开头,到后面的第一个换行符为止。也就是说,指令的长度仅限于一个逻辑行(预处理前,编译器会将多行物理行处理为一个逻辑行)。
编译器在预处理前的翻译处理:
I 编译器把源代码中出现的字符映射到源字符集,包括处理多字节字符和三字符序列。
II 代码物理行换行的处理,编译器定位每个反斜杆后面跟着换行符(按下Enter键在源代码文件中换行所生成的字符,而不是指符号表征\n)的实例,并删除它们,把多个物理行转换成一个逻辑行。因为预处理表达式的长度必须是一个逻辑行(一个逻辑行可以是多个物理行)。
III 编译器把文本划分成预处理记号(token,由空格、制表符或换行符分隔的项)序列、空白序列(一个空格替换所有空白序列,但不包括换行符)和注释序列(用一个空格替换)。
以上三步后,开始进行预处理阶段。
#define一般用来定义宏,做宏替换或头文件的保护性定义,分三类:
宏的名称不允许有空格,遵循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.
*/
与#运算符类似,##运算符可用于类函数宏的替换部分,能够连结字符串,组成一个新的标识符:
// 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
*/
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
*/
有些编程任务既可以用带参数的宏完成,也可以用函数完成。各自有适应的一些场合。
使用宏比使用普通函数复杂一些,稍有不慎会产生奇怪的副作用。
宏和函数的选择实际上是时间和空间的权衡。宏展开产生内联代码,略占空间,但没有普通函数的跳转,时间较省。特别是用在嵌套循环体内时。当然,在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)
#undef指令可以取消宏定义,如:
#define LIMIT 444
#undef LIMIT
#ifndef指令判断后面的标识符是否未定义,可以防止相同的宏被重复定义。但更常用来防止多次(重复)包含一个文件,也就是保护性定义:
#ifndef _HEADERNAME_H_
#define _HEADERNAME_H_
... // 头文件内容
#endif
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.
*/
在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-
,