预定义符号:
__FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#define
#define定义标识符:
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for( ; ; ) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf(“file:%s\tline:%d\t \
date:%s\ttime:%s\n” ,\
FILE,LINE , \
DATE,TIME )
注意:在预编译时#define之后的东西都会被直接替换,所以一般我们不会在最末尾加上 ; ,因为这样容易导致语法错误。
#define定义宏:
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常被我们称为宏(定义宏)。
#define name( parament-list ) stuff
其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中。
我们一般讲宏的名字全部写成大写,用于与函数名进行区分
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
#define SQUARE( x ) x * x int main() { printf("%d", SQUARE(5));//25 return 0; }
#define SQUARE( x ) x * x int main() { int a = 4; printf("%d", SQUARE(a + 1));//9 return 0; }
上面两个代码结果不同,因为第二个代码传过去的是4 + 1,而不是计算之后的结果5,所以用于对数值表达式进行求值的宏定义都应该加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
#define SQUARE( x ) ( ( x ) * ( x ) ) int main() { int a = 4; printf("%d", SQUARE(a + 1));//9 return 0; }
#define替换规则:
- 在调用宏时,首先对参数进行检查,看是否包含任何由#define定义的符号。如果包含,他们首先被替换;
- 替换文本后被插入到程序中原来的文本位置。对于宏,参数名被他们的值所替换;
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果包含,则重复上述操作。
- 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归;
- 当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。
宏和函数对比:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹
- 函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。而宏可以适用于整型、长整型、浮点型等类型。因为宏是类型无关的,但这也导致了宏是不够严谨的。
- 每次使用宏时,一份宏定义的代码将被插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏无法进行调试,并且可能会导致运算符优先级问题的出现,导致程序出错。
#undef
用于移除一个宏定义
#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
#if 常量表达式 //... #endif //常量表达式由预处理器求值。 如: #define __DEBUG__ 1 #if __DEBUG__ //.. #endif
多个分支的条件编译
#if 常量表达式 //... #elif 常量表达式 //... #else //... #endif
判断是否被定义
#if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
嵌套指令
#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif
文件包含
#include “filename”
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标
准位置查找头文件。
如果找不到就提示编译错误。
#include <filename>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
库函数也可以用""包含来查找,但是这样会先从本地文件中查找,效率会有一定的降低,并且如果本地文件与库文件有重名的,也不能进行区分
嵌套文件包含
comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。
这时我们可以使用条件编译解决这一问题。
我们一般使用#pragma once来避免头文件重复引入。
注:个人觉得#和##很鸡肋,所以没写。