C预处理器在程序执行之前查看程序。根据程序中的预处理器指令,预处理器把符号缩写替换成其表示的内容。
16.1翻译程序的第一步
首先,编译器把源代码中出现的字符映射到源字符集。第二,编译器定位每个反斜杠后面跟着换行符的实例,并删除它们。第三,编译器把文本划分成预处理记号序列、空白序列和注释序列。
16.2明示常量:#define
每行#define(逻辑行)都由3部分组成。第1部分是#define指令本身。第2部分是选定的缩写,也称为宏。有些宏代表值,这些宏被称为对象宏。宏的名称中不允许有空格,而且必须遵循C变量的命名规则:只能使用字符、数字和下划线字符,而且首字符不能是数字。第3部分称为替换列表或替换体。一旦预处理器在程序中找到宏的实例后,机会用替换体代替该宏。从宏变成最终替换文本的过程称为宏展开。
#define PX printf("x is %d.\n ",x)//预处理指令、宏、替换体
预处理器不做计算,不对表达式求值,它只进行替换。
16.3在#define中使用参数"
#define SQUARE(X) X*X
#define SQUARE(X) (X)*(X)
#define SQUARE(X) ((X)*(X))
注意,以上三种定义不同。在第一个中,如果X为x+2,则XX成了x+2x+2了。在第二个中,100/SQUAER(X+2)成了100/2*2 。要处理这两种情况,需要使用第三种定义。
一般而言,不要中宏中使用递增或递减运算符。
#define PSQR(X) printf("The square of X is %d.\n" ((X)*(X)));
PSQR(8);
输出为:
The square of X is 64
#define PSQR(X) printf("The square of "#x" is %d. \n",((X)*(X)));
int y=5;
PSQR(y);
PSQR(2 + 4);
该程序输出如下:
The square of y is 25.
The square of 2 + 4 is 36.
16.4宏和函数的选择
宏和函数的选择实际上是时间和空间的权衡。宏生成内联代码,即在程序中生成语句。如果调用20次宏,即在程序中插入20行代码。如果调用函数20次,程序中只有一份函数语句的副本,所以节省了空间。然而在另一方面,程序的控制必须跳转至函数内,随后返回主调程序,这明显比内联代码花费更多时间。
宏的一个优点是,不用担心变量类型(这是因为宏处理的是字符串,而不是实际的值)。因此,只要能用int或float类型都可以用SQUARE(X)宏。
对于简单的函数,程序员通常使用宏:
#define MAX(X,Y) ((X)>(Y)? (X):(Y))
#define ABS(X) ((X)<0? -(X):(X))
#define ISSIGN(X) ((X)=='+' || (X)=='-' 1:0)
要注意以下几点:
- 记住宏名中不允许有空格,但是在替换字符串中可以有空格。
- 用圆括号把宏的参数和整个替换体括起来。这样能确保被括起来的部分在下面,这样的表达式中正确地展开。
- 用大写字母表示宏函数的名称
- 如果打算使用宏来加快程序的运行速度,那么首先要确定使用宏和使用函数是否会导致较大差异。在程序中只使用一次的宏无法明显减少程序的运行时间。在嵌套中使用宏有利于提高效率。
16.5文件包含:#include
当预处理器发现#include指令时,会查看后面的文件名并把文件的内容包含到文件中,即替换源文件中的#indlude指令。这相当于把被包含文件的全部内容输入到
源文件#include指令所在的位置。
在UNIX系统中,尖括号告诉预处理器在标准系统目录中查找该文件。双括号告诉预处理器首先在当前目录中查找该文件。
头文件最常用的形式如下:
- 明示常量
- 宏函数
- 函数声明
- 结构模板定义
- 类型定义
16.6其他指令
- #undef指令 用于取消已定义的#define指令
- #ifdef、#else和#endif指令
- #ifndef指令
16.7内联函数
inline static void eatline()
{
while(getchar()!='\n)
contine;
}
inline必须与函数定义放在一起才能使函数称为内联
设计内联函数的动机:
内联扩展是一种特别的用于消除调用函数时所造成的固有的时间消耗方法。一般用于能够快速执行的函数,因为在这种情况下函数调用的时间消耗显得更为突出。这种方法对于很小的函数也有空间上的益处,并且它也使得一些其他的优化成为可能。
没有了内联函式,程式员难以控制哪些函数内联哪些不内联;由编译器自行决定是否内联。加上这种控制维度准许特定于应用的知识,诸如执行函数的频繁程度,被利用于选择哪些函数要内联。
此外,在一些语言中,内联函数与编译模型联系紧密:如在C++中,有必要在每个使用它的模块中定义一个内联函数;与之相对应的,普通函数必须定义在单个模块中。这使得模块编译独立于其他的模块
内联函数应该比较短小。把较小的函数变成内联并未节约多少时间,因为执行函数体的时间比调用函数的时间长得多。编译器优化内联函数必须知道函数定义的内容。这意味着内联函数定义与函数调用必须在同一个文件中。鉴于此,一般情况下内联函数都具有内部链接。
qsort()函数
函数声明:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void , const void))
参数:
- base–指向要排序的数组的第一个元素的指针
- nitems–base指向的数组元素的个数
- size–数组中每个元素的大小,以字节为单位
- compar–用来比较两个元素的函数
compare参数
compar参数指向一个比较两个元素的函数。比较函数的原型应该像下面这样。注意两个形参必须是const void *型,同时在调用compar 函数(compar实质为函数指针,这里称它所指向的函数也为compar)时,传入的实参也必须转换成const void *型。在compar函数内部会将const void *型转换成实际类型。
注意C和C++中的void*
C和C++对待指向void的指针有所不同。在这两种语言中,都可以把任何类型的指针赋给void类型的指针。C++要求把void*指针赋给任何类型的指针时必须进行强制类型转换。
断言库
asser.h头文件支持的断言库是一个用于辅助调试程序的小型库。它由assert()宏组成,接受一个整形表达式作为参数。如果表达式求值为假,assert()宏就在标准错误流(stderr)中写入一条错误信息,并调用abort()函数终止程序。