C Primer Plus(十六)

第十六章 C预处理器和C库16.1 翻译程序的第一步

对程序做预处理前,编译器会对它进行几次翻译处理。编译器首先把源代码中出现的字符映射到源字符集。第二,编译器查找反斜线后紧跟换行符的实例并删除这些实例。也就是将两个物理行printf(“That’s wond\erful!\n”);转换成一个逻辑行:printf(“That’s wonderful!\n”);接下来,编译器将文本划分成预处理的语言符号序列和空白字符及注释序列。编译器用一个空格字符代替每一个注释。最后程序进入预处理阶段。预处理器寻找可能存在的预处理指定。这些指令由一行开始出的#符号标识。

16.2 明显常量:#define

预处理指定用#符号作为行的开头。指令可出现在源文件的任何地方。指令定义的作用域从定义出现的位置开始直到文件的结尾。每个#define行由三部分组成。第一部分为指令#define自身。第二部分为所选择的缩略语,这些缩略语被称为宏。像本例中的这些宏用来代表值,它们被成为类对象宏。宏的名字中不允许有空格,而且必须遵循C变量命名规则。第三部分称为替换列表或主体。预处理器在程序中发现了宏的实例后,总会用实体代替该宏。从宏变成最终的替换文本的过程成为宏展开。

使用#define的时候可以使用多个物理行,一行结尾加反斜线符号以使该行扩展至下一行,注意第二行要左对齐,否开头空格也会作为主体的一部分。一般而言,预处理器发现程序中的宏后,会用它的等价替换文本代替宏,如果字串中还包括红,则继续替换。例外情况是双引号中的宏printf(“TWO:OW”);将输出TWO:OW而不是主体。

16.2.1 语言符号

从技术方面看,系统把宏的主体当作语言符号类型字符串,而不是字符型字符串。#define FOUR 2*2有一个语言符号:2*2。#define SIX 2 * 3有三个语言符号:2、*和3。当主体解释为字符型字符串时,预处理器用2 * 3替换SIX,也就是额外的空格也当作替换文本的一部分。但是当主体解释为语言符号类型时,预处理用由单个空格分隔的三个语言符号,即2 * 3来代替SIX。用字符型字符串的观点看,空格也是主体的一部分;而用语言符号字符串的观点看,空格只是分隔主体中语言符号的符号。C编译器处理语言符号的方式比预处理器的处理方式更加复杂。

16.2.2 重定义常量

假设您把LIMIT定义为20,后来在该文件中又把LIMIT定义为25.这个过程被称为重定义常量。ANSI标准只允许新定义与旧定义完全相同。这意味着主体具有相同顺序的语言符号。下面两个定义相同:#define SIX 2 * 3#define SIX 2 * 3两者都有三个相同的语言符号,而且额外的空格不是主体的一部分,下面的定义则被认为是不同的:#define SIX 2*3上式只有一个语言符号,因此与前面两个定义不相同。可以使用#undef指令重新定义宏。

16.3 在#define使用参数

通过使用参数,可以创建外形和作用都与函数相似的类函数宏。宏的参数也用圆括号括起来。例如#define SQUARE(X) X*X在程序中可以使用z=SQUARE(2);一个例子程序:

#include<stdio.h>#define SQUARE(X) X*X#define PR(X) printf(“The result is %d.\n”,X)int main(void){int x=4;int z;printf(“x=%d\n”,x);z=SQUARE(x);PR(z);PR(SQUARE(x+2));PR(100/SQUARE(2));printf(“x=%d\n”,x);PR(SQUARE(++x));return 0;}

这和我们期待的结果不一样,香港空间,原因在于预处理器不进行计算,而只进行字符串替换。早出现x的地方,预处理器都用字符串x+2进行替换,因此x*x变成x+2*x+2想改变结果,只需要多加几个圆括号 例如#define SQUARE(X) (X)*(X)

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

引号中的字符串中的宏被看作普通文本,香港服务器,而不是被看作一个可被替换的语言符号。假设您确实希望在字符串中包含宏参数,可以使用#。如果x是一个宏参量,那么#x可以把参数名转化为相应的字符串。这个过程称为字符串化。

#define PR(X) printf(“The square of ” #X ” is %d.\n”,(X)*(X))int y=5;PR(y);它的替换结果为printf(“The square of ” “y” ” is %d.\n”,(y)*(y));接着,香港服务器租用,字符串连接功能将这三个相邻的字符串转换为一个字符串:输出The square of y is 25.

16.3.2 预处理器的粘合剂:##运算符

和#运算符一样,##运算符可以用于类函数宏的替换部分。另外##还可用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如 #define XNAME(n) x ## n通过这个XNAME(4)的宏调用会展开成下列形式:x4

16.3.3 可变宏:…和_ _VA_ARGS_ _

实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个句号)。这样预定义_ _VA_ARGS_ _就可以被用在替换部分中,以表明省略号代表什么。例如#define PR(…) printf(_ _VA_ARGS_ _)PR(“Hello”);就展开一个参数“Hello”;#definePR(X,…)printf(“Message ” #X “: “_ _VA_ARGS_ _)double x=48;PR(1,”x=%g\n”,x);输出如下 Message 1: x=48

16.4 宏,还是函数

宏与函数间的选择实际上是时间与空间的权衡。宏产生内联代码,也就是在程序中产生语句。如果使用宏20次则插入20行代码。如果使用函数20次,那么程序中只有一份函数语句的拷贝,因此节省了空间。但是函数设计到程序控制的转移,因此花费时间更多。宏的一个优点是它不检查其中的变量类型,但是不注意的话会产生奇怪的现象。

16.5 文件包含:#include

预处理器发现#include指令后,就会寻找后跟的文件名并把这个文件的内容包含到当前文件中,就行您把被包含文件中的全部内容键入到源文件的这个特定位置一样。习惯上使用后缀.h表示头文件,这类文件包含置于程序头部的信息。包含大型头文件并不一定显著增加程序的大小。很多情况下,头文件中的内容是编译器产生最终代码所需的信息,而不是加到最终代码里的具体语句。浏览任何一个标准头文件都会使您对头文件中信息的类型有一个清晰的概念。另外,许多使用头文件来声明多个文件共享的外部变量。

16.6 其它指令

16.6.1 #undef指令

#undef指令取消定义一个给定的#define,即使没有定义,也可以取消。例如#define LIMIT 400 #undef LIMIT

16.6.2 已定义:C预处理器的观点

而消极的人则在每个机会都看到某种忧患。

C Primer Plus(十六)

相关文章:

你感兴趣的文章:

标签云: