C语言基础-预处理
1.1 预处理器和预定义符号
- 编译一个c程序第一步就是预处理,预处理就是源代码编译前对其进行一些文本操作。
- 主要的任务为删除注释,插入被#include的文件内容,定义和替换#define指令定义的符号,确定代码的部分内容是否应该根据一些条件编译指令进行编译 。
预处理器符号:
| 符号 | 示例值 | 含义 |
|---|---|---|
| __FILE__ | "name.c" | 进行编译的源文件名--调试有用 |
| __LINE__ | 23 | 文件当前行号--调试有用 |
| __DATE__ | "jan 22 2022" | 文件被编译的日期 |
| __TIME__ | "20:31:12" | 文件被编译的时间 |
| __STDC__ | 1 | 编译器遵循 ANSI C 其值就是1 否则未定义 |
例子:printf( "File %s line %d :", __FILE__, __LINE__ );
1.2 define
它的正式描述:#define name stuff
相邻的字符串常量被自动连接为一个字符串。
例子:
#define debug_test \
printf( "File %s line %d :" \
"x=%d, y=%d, z%d", \
__FILE__, __LINE__, \
x, y, z )
1.2.1 宏
define机制有一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(defined macro)
宏的声明形式:#define name(paremeter-list) stuff
parameter-list是一个由逗号隔开的的符号列表,他们可能出现在stuff中。
参数列表的左括号必须跟name紧邻,否者参数列表被解释为stuff的一部分。
实例:
#define square(x) x * x
square( 5 ) == 25;
square( a + 1 ) 相当于 a + 1 * a + 1 和我们预计的不符
更改宏定义:
#define square(x) (x) * (x)
则 square( a + 1 ) 相当于 (a+1)*(a+1)
下一个例子:
#difine add(x) (x) + (x)
x * add(3) 相当于 x * (3) + (3); 和我们预期不符
改宏定义:
#define add(x) ((x) + (x))
x * add(3) 相当于 x * ((3)+(3))
1.2.2 #define 替换
宏参数和宏定义可以包含其他#define定义的符号。但是宏不可以出现递归。
如果想把参数插入到字符串常量中去有两种技巧。
- 利用临近字符串自动连接的特性。
#define PRINT(format,value) \
printf( "the value is" format "\n", value)
PRINT( "%d", x + 3);
- 使用预处理器把宏参数转换为字符串。 #argument 这种结构被翻译成argument
#define PRINT(format,value) \
printf( "the value of " #value \
" is " format "\n", value )
PRINT( "%d", x + 3 );
产生结果: the value of x + 3 is 25
#define还有一种 ##结构,把位于自己两边的符号连接在一起成为一个合法的标识符,如果不能其结果就是未定义的。例子:
#define add_sum( sum_number, value ) \
sum##sumber += value
add_sum( 5, 25); 相当于执行 sum5 += 25;
1.2.3 宏与函数
宏非常适合定义执行简单的运算。
如:#define MAX( a, b) ( (a) > (b) ? (a) : (b) )
如果使用函数来完成这个任务,用于调用和从函数返回的代码可能比实际执行这个小程序的代码计算工作还要大。还有宏定义的可以比较任何类型,而函数会有限制。
宏还可以实现一些函数无法实现的功能。
#define MALLOC( n, type) \
( (type *)malloc( (n) * sizeof(type) ) )
宏的缺点就是每次使用宏就会将宏定义代码插入到程序中,增加程序的长度。
1.2.4 带副作用的宏参数
如 x++ ,getchar()等。警惕。
1.2.5 宏命名约定 :宏定义名字全都大写,区分函数
1.2.6 #undef name 用于移除一个宏定义
1.2.7 命令行定义
许多c编译器提供了一种功能,允许在命令行中定义符号,用于启动编译过程。当通过同一个源文件编译一个程序的不同版本时,非常有用。
1.3 条件编译(conditional compilation)
使用条件编译可以选择一部分代码是被正常编译还是完全忽略。下面是它的语法定义:
#if constant-expression
statement
#endif
//#if 指令还具有可选的#elif
//使用时,每个版本当前所需要的特性符号被定义为1,其余定义为0
#if constant-expression
statememt
#elif constant-expression
statement1
#elif constant-expression
...
#else
statement_n
#endif
PS: constant-expression 即常量表达式,由预处理器进行求值,如果值是非零,则statement部分就被编译;反之就不编译。
常量表达式要么是一个字面值,要么是一个由#define 定义的符号。
#define DEBUG 1
#if DEBUG
printf( "x=%d, y=%d", x, y );
#endif
1.3.1 测试符号是否被定义
#if defined(symbol) 等价于 #ifdef symbol //#if的形式功能更强
#if !defined(symbol) 等价于 #ifndef symbol
//常量表达式可能含有额外的条件
#if x > 0 || defined( ABC ) && defined( BCD )
//这些指令可以嵌套使用
#if defined( OS_UNIX )
#ifdef OPTION1
unix_version_of_option1();
#endif
#ifdef OPTION2
unix_version_of_option2();
#endif
#elif defined( OS_MSDOS )
#ifdef OPTION1
msdos_version_of_option1();
#endif
#endif // OS_UNIX
1.4 文件包含 #include
- 一个头文件被包含到n个源文件中,它实际上被编译了n次。这个事实意味着为了减少开销,每个头文件只应该包含一组函数或者数据声明。
- 库函数文件和本地文件。
- 处理本地文件常见策略就是在源文件所在当前目录进行查找,没找到编译器就像查找函数库文件一样在标准位置查找本地头文件。
- 本地文件的绝对路径(absolute pathname)方式
#include "/home/c/pro/declar.h"
1.4.1 嵌套文件包含
在一个将被其他文件包含的文件中使用#include。最好不要让嵌套深度超过一层或两层。
1.5 其他预处理指令
/*
#error允许生成错误信息
#error text of error message
*/
#if defined( OS_UNIX )
#ifdef OPTION1
unix_version_of_option1();
#endif
#ifdef OPTION2
unix_version_of_option2();
#endif
#elif defined( OS_MSDOS )
#ifdef OPTION1
msdos_version_of_option1();
#endif
#else
#error no option selected!
#endif // OS_UNIX
浙公网安备 33010602011771号