对程序作与处理前,编译器会对它进行几次翻译处理:
每个 #define 行(逻辑行)由三部分组成:
预处理器在程序中发现宏后,会用替换文本代替该宏。例外情况是双引号中的宏。从宏变成最终的替换文本的过程称为宏展开。
预处理器不进行计算,只按照指令进行文字替换操作。
从技术方面看,系统把宏的主体当作语言符号(token)类型字符串,而不是字符型字符串。C 预处理器中的语言符号是宏定义主体中单独的词(word),词与词之间用空白字符分开。
从语言符号字符串的观点看,空格只是分隔主体中语言符号的符号;从字符型字符串的观点看,空格也是主体的一部分。
ANSI C 标准只允许新定义与就定义完全相同,即主体具有相同顺序的语言符号。例如,下面两个定义相同:
#define SIX 2 * 3
#define SIX 2 * 3
通过使用参数,可以创建外形和作用都与函数相似的类函数宏(function-like macro)。宏的参数用圆括号括起来。如:
#define SQUARE(X) X*X //类函数宏定义
num = SQUARE(2); //使用
SQUARE 为宏标识符,SQUARE(X) 中的 X 为类函数宏的参数,X*X 为替换列表。预处理器在程序中发现宏后,会用替换文本代替该宏(只进行文本替换,而不进行计算)。
# 运算符可以把语言符号转化为字符串,该过程称为字符串化。
#define SQUARE(X) printf("The squuare of "#x" is %d.\n", ((X)*(X))) //定义
PSQR(5); //调用。调用时用"5"代替了#X
The squuare of 5 is 25. //输出结果
## 运算符可以把两个语言符号组和成一个语言符号。
#define XNAME(X) n ## X //定义
XNAME(5); //调用
n5 //输出结果
实现思想:在宏定义中参数列表的最后一个参数为省略号(...)。这样预定义宏 __VA_ARGS__ 就可以被利用在替换部分中。
#define PR(X,...) printf("Message "#X": "__VA_ARGS__) //定义
PR(5,"MrGold.\n"); //调用
Message 5: MrGold. //输出结果
宏与函数间的选择实际上是时间与空间的权衡。
对于宏的几点注意:
预处理器发现 #include 指令后,就会寻找后跟的文件名并把这个文件的内容包含到当前文件中。被包含的文件中的文本将替换源代码文件中的 #include 指令,相当于被包含文件中的全部内容键入到源文件中的这个特定位置。 #include 指令有两种形式:
#include //文件名放在尖括号中,优先搜索系统目录
#include "mystuff.h" //文件名放在双引号中,优先搜索本地目录
头文件包含置于程序头部的信息。头文件经常包含预处理语句。头文件可由系统提供,也可以自己创建。
头文件内容最常见的形式包括:
首先,通常可以在多个不同位置找到库函数。其次,不同的系统使用不同的方法搜索这些函数。
数学库包含许多有用的数学函数。头文件 math.h 提供这些函数的函数声明或原型。
通用工具库包含各种函数,其中包括随机数产生函数、搜索和排序函数、转换函数和内存管理函数。头文件 stdlib.h 提供这些函数的函数声明或原型。
诊断库是设计用于辅助调试程序的小型库,由头文件 assert.h 支持。它由宏 assert() 构成,assert() 宏的作用为:表示出程序中某个条件应为真的关键位置,并在条件为假时用 assert() 语句终止该程序。
memcpy() 和 memmove() 函数为赋值数组提供了便利工具。下面是两个函数的原型:
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);
这两个函数均从 s2 指向的位置复制 n 字节数据到 s1 指向的位置,且均返回 s1 的值。两者间的差别由关键字 restrict 造成:
头文件 stdarg.h 为函数提供了“可以接受可变个数的参数”的能力。但必须按照如下步骤进行:
一个实例:
double sum(int lim, ...) //最右边的参量(省略号前)用parmN表示,传递给该参量的实际参数值是省略号部分代表的参数个数。
va_list ap; //创建一个va_list类型的变量ap,用于存放参数。
va_start(ap, lim); //使用宏va_start()初始化为参数列表。宏va_start()接受两个参数:
//第一个参数为va_list类型的变量(此处为ap),第二个参数为参量parmN(此处为lim)。
va_arg(ap,double); //使用宏va_arg()访问参数列表中的内容。宏va_start()接受两个参数:
//第一个参数为va_list类型的变量(此处为ap),第二个参数为一个类型名
va_end(ap); //使用宏va_end()完成清理工作。va_end()接受一个va_list类型的变量(ap)。
C99 添加了宏 va_copy() 来保存 va_list 变量的副本。宏 va_copy() 的两个参数均为 va_list 类型的变量,它将第二个参数复制到第一个参数中。例如:va_copy(apcopy, ap); //apcopy 是 ap 的一个副本。