小结
概念
■ 编译预处理主要是指在编译之前由预处理器对源代码进行的一些文字加工处理工作。编译预处理根据程序的源文件得到供编译器编译的不含预处理命令的翻译单元(Translation Unit)。
■ 使用预处理命令可以提高编程的效率,增强代码的可读性、可维护性、可移植性,改善代码的结构,便于调试及有条理地管理源程序代码。
■ 预处理命令以“#”开头,“#”之前或之后容许有空白字符。
■ 预处理命令以行为单位。
■ 预处理不是一次性完成的,而是分为几个阶段逐步完成。
■ 预处理器会把相邻的字符串字面量连接为一个。
■ 编译预处理命令“#include<文件名>”或“#include"文件名"”的作用是把“文件名”的文件的内容复制、粘贴到命令出现处。
■ 文件包含命令中使用“<>”括住文件名表示根据编译器规定在特定位置寻找文件,使用“""”括住文件名通常表示先搜索源文件所在的位置。
■ C语言的源程序可以由若干源文件组成。
■ 把源程序分割成若干源文件可以减少代码各个部分之间的耦合,因而可以降低问题的难度,使得源程序更容易管理。无论对于源程序的编辑、编译,还是测试和调试都具有很重要的意义。在团队开发模式下,把源程序分割成若干源文件是必然的。
■ “#include”预处理命令所包含的头文件是保证两个模块之间协调一致的纽带和关键。所包含的文件中的内容通常有数据类型的定义、函数原型和公用的宏定义。
■ 可以用下面的技术保证文件不被重复包含。
其中的“#ifndef”和“#endif”都是条件编译预处理命令。
■ 其他条件编译预处理命令还有“#ifdef”、“#if”、“#else”、“elif”,其功能是确保某段代码被编译或不被编译,其使用方法与if-else语句相仿,但“#if”和“elif”后面只可以是常量表达式。
■ “defined”是一个编译预处理运算符。“#if defined标识符”;等价于“#ifdef标识符”;“#if!defined(标识符)”等价于“#ifndef标识符”。
■ C语言有两种形式的宏:类似对象的宏和类似函数的宏。
■ 定义类似函数的宏时,宏名后必须紧跟一“(”。
■ 类似对象的宏的宏名被直接用宏体替换。
■ 类似函数的宏先被用宏体展开,然后替换掉其中出现的参数。
■ 代码中的字符串字面量、字符常量、注释、#include的文件名及其他常量中的内容由于并不是标识符所以在其内部不发生宏替换。
■ 由于合并物理行是在预处理之前进行,所以很长的预处理命令可以在形式上写成多行,后面跟一表示“续行”的符号:“\”。这样在逻辑含义上,它依然是一行。
■ 应该避免与编译器事先已经定义的宏重名。一个比较保险的做法是避免使用下划线开头的宏名。
■ 调用宏不应该或者至少应该极其谨慎地使用具有副效应的表达式作为参数。
■ 定义在函数外部的变量叫做外部变量(External Variables )。外部变量的生存期间是从程序运行开始到程序结束。如果外部变量在定义时如果没有被初始化,那么其初值是这个变量被赋值为0的结果。
■ static存储类别的外部变量的有效范围是从变量定义的位置到变量定义所在的文件结束处;extern存储类别外部变量的有效范围可以是整个源程序(包括构成源程序中的其他源文件)。
■ 一般情况下,使用外部变量之前需要有变量“说明”,以说明该变量已经在别处定义过。
■ C语言以extern作为外部变量的默认的存储类别。
■ 二元预处理运算符“##”用于在宏定义中构造特定的“预处理单词”(Preprocessing Token)。这个运算符不可以出现在替换列表的开头和结尾。
■ “#undef”预处理命令用于规定某个宏定义的结束位置。
■ 预定义的宏不可以通过“#undef”命令取消。
■ 函数原型前面的“static”的含义是这个函数是static存储类别,其定义在文件后面。
■ 函数定义前面出现的关键字“static”的含义是表明该函数只可以在模块内部也就是翻译单元(Translation Unit)内部被调用。
■ 一元预处理运算符“#”用于把某个宏参数替换为相应的字符串字面量。
■ C99容许定义参数数目不定的宏。这些不确定的形参用“…”表示;在对应的替换表中,这些参数出现的位置用预定义的宏“VA_ARGS”表示。
■ #pragma预处理命令的作用是给编译器提供一些额外的编译信息。
■ “_Pragma”是一个预处理运算符,其作用和“#pragma”相似。“_Pragma”不需要单独占据一行,且可以很容易地通过宏展开实现“#pragma”命令的“参数化”。
■ #line预处理命令用于重新指定代码的行号,且可以给所在的源文件指定一个别名。
■ #error预处理命令的作用是通知预处理器停止预处理也不继续进行编译,并输出一个错误信息。
常见错误
■ #defineA=3
■ #defineA=3;
■ 错误地把数据类型定义写在函数原型前。
风格
■ 宏定义不一定需要有完整的配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。
■ 在两个模块交互引用对方头文件的情况下,把“#include”命令写在自己头文件中数据类型定义与函数原型之间的部分。
牛角尖
■ 有时,编译器可能提供同名的宏和函数。这时如果希望调用函数而不是宏,可以用下面的方法。
或者先使用预处理命令“#undef宏名”终止宏,然后正常地调用函数。或者可以把函数名赋值给一个同类型的指针变量,如下所示。
然后再通过这个指针调用函数。
这些做法都利用了宏名后面不可能跟“)”的特性。
■ “#include”命令中包含的文件的文件名的规则事实上是与编译器有关的。有些编译器可能只容许文件名由字母和数字构成,且可能对文件名的长度有所限制。