首页 > 代码库 > C和指针 (pointers on C)——第十四章:预处理器

C和指针 (pointers on C)——第十四章:预处理器

第十四章 预处理器


我略过了高级指针话题那一章。太多的小技巧和太多的学术性探讨并不适合现在的我。不过我确实是读了,很多地方没有读懂,如果谁读了私下可以交流一下。有的小技巧还是非常有意思。
预处理器这一章的内容,大家肯定都用过,什么#include,#define #ifdef #undef这些,但是绝对用的不多。作为全面了解学C,还是应该都看一看。
预处理器用法很讲究,用不好会失误,用好了会大大加快运行时速度(不是编译速度)。


总结:
C程序的第一个步骤就是预处理,预处理器共包含以下几个符号:
1、#define
定义宏,替换作用。
它把一个符号名与任意的字符序列联系在一起。例如,这些字符可能是一个字面值常量、表达式或者程序语句。这个语句到末尾结束,不需要;
如果过长的话可以用一个反斜杠,如:
#define PRINT(FORMAT,VALUE) \
printf("The value of " #VALUE\
" is " FORMAT "\n", VALUE)
...
PRINT("%d", X+3);
output: The value of x+3 is 25.
这里面#VALUE相当于把一个宏参数转换为一个字符串。
有些可以用函数实现的也可以用宏实现,宏的优点就在于它与类型无关。而且宏的执行速度快于函数。因为他不存在函数调用返回的开销,在编译的时候就已经弄好了。
缺点就是使代码过长,同时具有不可预测的副作用,函数在这方面更容易预测。比如可能会执行多次(意料之外),可能会涉及优先级的问题。
两者不同也在于,命名约定也不同,比如max(a,b);和MAX(a,b);


2、#ifdef #undef #endif
属于条件编译,#undef指令可以使一个原来定义好的名字被忽略。


3、#if #elif #else
这三者也属于条件编译,分别是这样,如果(#if),当满足什么条件就怎样(#elif),如果都不成立就(#else)。
功能较#ifdef这组更加强大,有点儿像switch case语句。


4、#include 
实现文件包含 。
经常有这两种形式#include "XXX.h"或者#include <XXX.h>,区别在于,""是在源文件路径下的头文件,<>是库文件路径下的头文件。
文件包含可以嵌套,但是不要超过两层。因为会造成多次包含同一个文件的危险。
有人说这个容易,增加个:
#ifdef _HEADERNAME_H
#define _HEADERNAME_H
...
#endif
这个经常出现在大型程序中,但是会增加编译速度。


5、#error,#line,#progma
#error,在编译的过程中会产生一条错误信息,新信息中包含的是你所选择的文本。
#line,告诉编译器下一输入的行号。
#progma,语法因特性而异。


警告:
1、不要在一个宏定义的末尾加上分号。
2、在宏定义中使用参数,但忘了在他们周围加上括号。
编程原则上,宏定义参数需要各种加括号的。
3、忘了在整个宏定义的两边加上括号。
2和3均会因为优先级的问题造成不可预知的错误。


编程提示:
1、避免用#define指令定义可以用函数实现的很长序列的代码。
程序的长度会极大极大的增长!!
2、在那些对表达式求值的宏中,每个宏参数出现的地方都应该加上括号,并且在整个宏定义的两边也要加上括号。
3、避免使用#define宏创建一种新语言。
创建以后可能最终连你都不知道这种语言是什么了。
4、采用命名约定,使程序员很容易看出某个标识符是否为#define宏。
5、只要合适就应该使用文件包含,不必担心它的额外开销。
这个开销真的很小,而且只存在与编译中,运行中是没有的。
6、头文件只应该包含一组函数或者数组的声明。
7、把不同集合的声明分离到不同的头文件中可以改善信息隐藏。
8、嵌套的#include文件使我们很难判断源文件之间的依赖关系。
所以尽量减少#include文件的嵌套。


问题:
1、#define与字面值常量的优点。
可读性好,可以给常量起个自己能看懂的名字。
如果想修改常量的数值,只需要修改define的内容就行,方便。
2、编写一个用于调试的宏,打印出任意的表达式,它被调用时应该接受两个参数。第1个是printf格式码,第2个是需要打印的表达式。
#define DEBUG_PRINT(format, value)  printf("File %s, line %d: %s = " format "\n",\
__FILE__, __LINE__, #value, value);