C语言学习第三十三天,今天主要学习C预处理器。
4.11 C预处理器
预处理器时编译过程中单独执行的第一个步骤。两个最常用的预处理器指令是:#include指令(用于在编译期间把指定文件的内容包含进当前文件中)和#define指令(用任意字符序列替代一个标记)。还有一些条件编译与带参数的宏。
4.11.1 文件包含
文件包含指令(即#include指令)使得处理大量的#define指令以及声明更加方便。在源文件中,任何形如:
#include "文件名"
或
#include <文件名>
的行都将被替换为由文件名指定的文件的内容。如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名是用<>括起来的,则将根据相应的规则查找该文件,这个规则同具体的实现有关。被包含的文件本身也可包含#include指令。
源文件的开始处通常都会有多个#include指令,它们用以包含常见的#define语句和extern声明,或从头文件中访问库函数的函数原型声明,比如<stdio.h>。
4.11.2 宏替换
宏定义的形式如下:
#define 名字 替换文本
后续 所有出现名字记号的地方都将被替换为替换文本。通常情况下,#define指令占一行,替换文本是#define指令行尾部的所有剩余部分内容,但也可以把一个较长的宏定义分成若干行,这时需要在待续行末尾加上一个反斜杠符\。#define指令定义的名字的作用域从其定义点开始,到被编译的源文件的末尾处结束。宏定义中也可以使用前面出现的宏定义。替换只对记号进行,对括在引号中的字符串不起作用。例如,如果YES是一个通过#define指令定义过的名字,则在printf("YES")或YESMAN中将不执行替换。
替换文本可以是任意的,例如:
#define forever for (; ;) /* 无限循环 */
该语句为无限循环定义了一个新名字forever。
宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。例如,下列宏定义定义了一个宏max:
#define max(A, B) ((A) > (B) ? (A) : (B))
使用宏max看起来很像是函数调用,但宏调用直接将替换文本插入到代码中。形式参数(在此为A或B)的每次出现都将被替换成对应的实际参数。因此,语句:
x = max(p+q, r+s);
将被替换为下列形式:
x = ((p+q) > (r+s) ? (p+q) : (r+s));
如果对各种类型的参数的处理是一致的,则可以将同一个宏定义应用于任何数据类型,而无需针对不同的数据类型定义不同的max函数。要适当使用圆括号以保证计算次序的正确性。
可以通过#undef指令取消名字的宏定义,这样做可以保证后续的调用是函数调用,而不是宏调用:
#undef getchar
int getchar(void) { ... }
形式参数不能用带引号的字符串替换。但是如果在替换文本中,参数名以#作为前缀则结果及爱给你被扩展为由实际参数替换该参数的带引号的字符串。例如,可以将它与字符串连接运算结合起来编写一个调试打印宏:
#define dprint(expr) printf(#expr " = %g\n", expr)
使用语句
dprint(x/y);
调用该宏是,该宏将被扩展为:
printf("x/y" " = %g\n", x/y);
其中的字符串被连接起来了,这样宏调用的效果等价于
printf("x/y = %g\n", x/y);
预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对 替换后的结果重新扫描。例如,下面定义的宏paste用于连接来给你个参数:
#define paste(front, back) from ## back
因此,宏调用paste(name, 1)的结果将建立记号name1。
练习4-14 定义宏swap(t, x, y)以交换t类型的两个参数。(使用程序块结构会对你有所帮助)
#include <stdio.h>
#define swap(t, x, y) {\
t temp; \
temp = y; \
y = x; \
x = temp; \
}
main() {
int x = 1;
int y = 2;
swap(int, x, y);
printf("x = %d, y = %d", x, y);
}