1. #define的用法
#define指令把一个符号名与任意的一个字符序列联系在一起
#define name stuff;
//每当有符号name出现在这条指令之后,预处理器就会把它替换成stuff
#define reg register;
//为关键字register声明一个简短的别名
#define do_forever for( ; ; );
//声明一个更具描述性的符号来代替无限循环的for语句类型
#define CASE break;case
//用于switch中,自动把一个break放在每个case之前
//如果定义的stuff很长,可以分成几行,出最后一行外,每一行的末尾都要有"/"
#define DEBUG_PRINT printf(..., \
..., \
...)
2. 宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或者定义宏
#define SQUARE1(x) x * x;
SQUARE1(5) //被替换为5*5,结果为25
a = 5;
SQUARE1(a + 1) //被替换为a+1*a+1,结果是11
#define SQUARE2(x) (x)*(x);
SQUARE2(a + 1); //被替换为(a+1)*(a+1),结果是36
#define DOUBLE1(x) (x) + (x);
a = 5;
10 * DOUBLE1(5); //被替换为10*(a)+(a),结果是55
#define DOUBLE2(x) ((x) + (x)); //被替换为10*((a)+(a)),结果是100
3. 宏不可以出现递归
##结构把位于它两边的符号连接成一个符号,并允许宏定义从分离的文本片段创建标识符
假设程序中已经定义了这样一个带参数的宏:
#define paster(n)
printf("token"#n" = %d", token##n)
//同时又定义了一个整形变量:
int token9 = 9;
//现在在主程序中以下面的方式调用这个宏:
paster(9);
//那么在编译时,上面的这句话被扩展为:
printf("token" "9" " = %d", token9);
注意到在这个例子中,paster(9);中的这个”9”被原封不动的当成了一个字符串,与”token”连接在了一起,从而成为了token9,而#n也被”9”所替代。
可想而知,上面程序运行的结果就是在屏幕上打印出token9=9
4. 副作用就是表达式求值时出现的永久性效果
x + 1; //可以重复执行,每次执行的结果都相同
x++; //每次x的值都会增加1,重复执行结果不同
getchar(); //也有副作用,这个函数被调用将会取走一个字符,后续再调用得到的是不同的字符
5. 宏和函数的区别
在使用宏和函数时语法相同,为了区别开来,采用命名约定,把宏的名字全部大写。
宏和函数的不同之处
6.
#undef 移除一个宏定义
#undef name 如果一个现存的名字需要重新定义,那么它的旧定义首先必须要用#undef移除。
7. 使用条件表已可以选择代码的一部分被正常编译还是完全忽略。用于支持条件编译的基本结构是#if指令和与其匹配的#endif指令。
#if constant-expression
statements;
#endif
constant-expression(常量表达式)由预处理器进行求值,如果它的值是非零值(真),那么statements就被正常编译。
所谓常量表达式,就是说它或者是字面值常量或者是一个#define定义的符号。如果变量在执行期无法获得它的值,那么它们如果出现在常量表达式中就是非法的,因为其值在编译时不可预测。
if DEBUG
printf("x = %d, y = %d\n", x, y);
#endif
如果想编译 #define DEBUG 1
//如果要忽略,将DEBUG定义为0
条件编译的另一个用途是在编译时选择不同的代码部分。为了支持这个功能,#if指令还具有可选的#elif和#else子句。
#if constant-expression
statements
#elif constant-expression //出现次数不限
other statements
#else
other statements
#endif
测试一个符号是否被定义(四种方法)
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_option1;
#endif
#ifdef OPTION2
unix_option2;
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_option2;
#endif
#endif
//为了阅读方便,可以为#endif添加注释标签,如下
#endif /* OPTION1 */
8. #include指令使另一个文件的内同被编译,就像它实际出现于#include指令出现的位置一样,这种替换执行的方式很简单:预处理器删除这条指令并用包文件的内容取而代之。这样,一个头文件如果被包含到10个源文件中,它实际被编译了10次。
当头文件被包含时,位于头文件内的所有内容都要背编译。这意味着每个头文件只应该包含一组函数或数据的声明,和把一个程序需要的所有声明都放入一个巨大的头文件相比,使用几个头文件,每个头文件包含用于某个特定函数或模块的声明的做法更好一些。
程序设计和模块化的原则也支持这种方法,只把必要的声明包含于一个文件中这种做法更好一些,这样文件中的语句就不会意外的访问应该属于私有的函数或变量。
9. 函数库文件包含
函数库头文件包含
编译器在由编译器定义的标准位置下搜索查找这个文件
#include <filename>
本地文件包含
先在源文件所在的当前目录下搜索,再像查找函数库头文件一样在标准位置查找本地头文件
#include "filename"
还可以使用绝对路径:UNIX中使用斜杠,MS_DOS中使用反斜杠
home/fh/proj/dec.h(UNIX)
嵌套文件包含
在一个将被其他文件包含的文件中使用#include指令是正常的。
例如函数的原型被放入头文件中,并用#include指令包含到需要使用这些函数的源文件中,但是每个使用I/O函数的文件必须同时包含stdio.h,因此包含函数原型的头文件中也可能包含一条#include
#include "a.h"
#include "b.h"
//若a.h和b.h中都出现了c.h,那么事实上出现了头文件的重复引入
要解决这个问题可以使用条件编,头文件可以如下编写
#ifdef HEADERNAME_H
#define HEADERNAME_H 1
//也可以写作#define HEADERNAME_H ,尽管现在是空字符串而不是1,但这个符号仍被定义
/*
**头文件中的具体代码
*/
#endif
那么多重半酣即被消除,当头文件第1次被包含时,它被正常处理,符号HEADERNAME_H被定义为1,如果头文件再次被包含,通过条件编译,它的所有内容被忽略。符号HEADERNAME_H按照被包含文件的文件名进行取名,以避免由于其他头文件使用相同的符号因此冲突。
2016.10.16