C预处理
C预处理器不是编译器的组成部分,它是编译过程中一个单独的步骤。本质上是一个文本替换工具
所有预处理器命令都是#开头,预处理指令不是语句,所以它们不会以分号结尾。
预处理:选项 gcc -E test.c -o test.i 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
常见指令:
#define,#include,#undef,#ifdef,#ifndef,#if,#else,#elif,#endif,#error,#pragma
预定义宏
宏 | 描述 |
---|---|
__DATE__ | 文件被编译日期,一个以 “MMM DD YYYY” 格式表示的字符常量。 |
__TIME__ | 文件被编译时间,一个以 “HH:MM:SS” 格式表示的字符常量。 |
__FILE__ | 当前文件名,一个字符串常量。 |
__LINE__ | 当前行号,一个十进制常量。 |
__STDC__ | 当编译器以 ANSI 标准编译时,则定义为 1。 |
#define
#define MAX 100 // 将所有MAX替换为100,增强可读性
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把break写上
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n", \
__FILE__,__LINE__, \
__DATE__,__TIME__) // 测试预定义宏
参数化的宏
格式:
#define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现
在stuff中。
注意:参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部
分。
替换过程:
- 对参数进行检查,是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程
注意事项:
由于宏替换的本质是文本替换所以在定义的时候需要注意符号优先级
#define SQUARE(x) x * x
#define SQUARE(x) ((x) * (x)) // 这样写会避免因符号优先级带来的错误
#与##
把一个宏的参数转换为字符串常量时,使用字符串常量化运算符 #
#define PRINT(FORMAT, VALUE) \
printf("the value of " #VALUE " is "FORMAT "\n", VALUE);
...
int i = 0;
PRINT("%d", i+3); // the value of i+3 is 3
宏定义内的标记粘贴运算符 ## 会合并两个参数
#define ADD_TO_SUM(num, value) \
sum##num += value;
...
int sum1 = 0;
int sum2 = 0;
ADD_TO_SUM(1, 3); // 给sum1变量增加3
ADD_TO_SUM(2, 4); // 给sum2变量增加4
// 由于宏定义本质是文本替换,所以以下操作是错误的
for (int i = 1; i < 3; ++i) {
ADD_TO_SUM(i,1); // 给sumi变量增加1,sumi未定义,所以会报错
}
宏和函数
- 宏的执行速度快,没有函数栈帧开销,但如果定义比较长的宏,代码长度会增加
- 宏的参数与类型无关,所以不存在类型检查
- 宏的书写比较复杂,需要考虑操作符优先级问题和副作用的参数
- 不方便调试,不可递归
命令行定义
// test.c
#include <stdio.h>
int main() {
printf("%d\n",MAX); // 直接编译会显示MAX未定义
}
可以这样编译gcc -DMAX=7 test.c
同一份代码,可以根据不同选项在不同环境下执行。