预处理器可以在编译前处理C程序
//include 指令告诉预处理器打开一个特定的文件 #include <stdio.h> //用define 指令定义了一个宏,用来代表其他东西的名字 #define PI 3.14 //可以用 / 符号换行 #define DISK_CAPACITY (SIDES * \ TRACKS_PER_SIDE * \ SECTORS_PER_TRACK * \ BYTES_PER_SECTOR) //带参数的宏 #define MAX(x,y) ((x)>(y)?(x):(y)) //展开后为 i = MAX(j+k, m-n) i = ((j+k)>(m-n)?(j+k):(m-n))
大多数预处理器都属于下面三种类型之一:
宏定义 #define指令定义一个宏, #undef指令删除一个宏定义
文件包含 #include指令导致一个指定文件的内容被包含到程序中
条件编译 #if、#ifdef、#ifndef、#elif、#else 和 #endif 指令可以根据预处理器可以测试的条件来确定是将
一段文本块包含到程序中还是将其排除在程序之外
适用于所有指令的规则
指定都以 # 开头
在指令的符号之间可以插入任意数量的空格或水平制表符
指令总是在一个换行符处结束,除非明确地指明要延迟,可以在末尾使用 \ 字符
指令可以出现在程序中的任何地方
注释可以与指令放在同一行
使用#define来为常量命名有许多优点
1.程序会更容易读
2.程序会更容易修改
3.可以帮助避免前后不一致或者键盘输入错误
4.可以对C语法做小的修改
5.对类型重命名
6.控制条件编译
带参数的宏优缺点
1.程序可能会更快
2.宏更通用(宏的参数没有类型,可以接收int,long,float等类型)
3.编译后的代码通常会变大
4.宏参数没有类型检查
5.无法用一个指针来指向一个宏
6.宏可能会不止一次地计算它的参数
//MAX宏的副作用 n = MAX(i++,j); //展开后为 n = ((i++)>(j)?(i++):(j)); //如果i大于j,那么i可能会增加两次
宏定义中的圆括号
#define SQ(y) y*y //如果参数y没有加括号,那么下面的表达式就会有问题 sq=SQ(a+1); //展开后为 sq=a+1*a+1; //所以需要携程 SQ(y) (y)*(y) //这里有两个规则 //1.如果宏的替换列表中有运算符,那么始终将替换列表放在括号中 #define TWO_PI (2*3.14) //如果不加括号,#define TWO_PI 2*3.14 在处理时就有问题 conversion_factor = 360/TWO_PI //展开后为 conversion_factor = 360/2*3.14 2.如果宏有参数,每个参数每次在替换列表中出现时都要放在圆括号中
宏的通用属性
1.宏的替换列表可以包含对其他宏的调用
#define PI 3.14 #define TWO_PI (2*PI)
2.预处理只会替换完整的记号,不会替换记号的片断
#define SIZE 256 int BUFFER_SIZE; if(BUFFER_SIZE > SIZE) { puts("error : SIZE exceeded"); } //展开后为 int BUFFER_SIZE; if(BUFFER_SIZE > 256) { puts("error : SIZE exceeded"); } //尽管标识符BUFFER_SIZE和字符串"error : SIZE exceeded"都包含SIZE,但他们没有被预处理影响
3.宏定义的作用范围通常到出现这个宏的文件末尾(定义在函数中的宏不是仅在函数内起作用,而是作用到文件末尾)
4.宏不可以被定义两遍,除非新的定义与旧的定义是一样的
5.宏可以使用#undef指令“取消定义”
# 和 ##运算符
// #运算符会将一个参数转换为字符串字面量 #define PINRT_X(n) printf(#n" = %d",n) PINRT_X(i+j); //展开后为 printf("i+j = %d",i+j); // ##运算符可以将两个记号(如标识符)“粘合”在一起 #define MK_ID(n) i##n int MK_ID(1), MK_ID(2), MK_ID(3); //展开后为 int i1, i2, i3; //由于c语言不允许同名函数,可以为每个max函数构造不同的名字 #define GENERIC_MAX(type) \ type type##max(type x, type y){ \ return x > y ? x:y; \ } GENERIC_MAX(float) //展开后为 float float_max(float x, floaty) { return x > y ? x:y; }
预定义宏(名字是两个下划线+name+两个下划线组成)
名字 | 描述 |
__LINE__ | 被编译的文件中的行号 |
__FILE__ | 被编译的文件名 |
__DATE__ | 被编译的日期(格式"Mmm dd yyyy") |
__TIME__ | 被编译的时间(格式"hh:mm:ss") |
__STDC__ | 如果编译器符合C标准(C89或C99), 那么值为1 |
C99中新增的预定义宏
名字 | 描述 |
__STDC_HOSTED__ | 如果是托管式实现,值为1;如果是独立式实现,值为0 |
__STDC_VERSION__ | 支持的C标准版本 |
__STDC_IEC_599__' | 如果支持IEC60599浮点算术运算,则值为1 |
__STDC_IEC_599)COMPLEX' | 如果支持IEC60599复数算术运算,则值为1 |
__STDC_ISO_10646__' | 如果wchar_t的值与指定年月的ISO 10646标准相匹配, 则值为yyyymmL |
条件编译
//定义一个宏 #define DEBUG 1 //if指令会计算常量表达式的值,如果是0那么#if与#endif之间的行将在预处理过程中 //从程序中删除;否则#if和#endif之间的行就会被保留在程序中 #if DEBUG printf("ok~~"); #endif //defined运算符用于检测某个宏是否被定义过,如果是则返回1;否则返回0 #if defined(DEBUG) printf("ok~~~"); #endif //也可以简写成 #if defined DEBUG //#ifdef指令用于测试一个标识符是否已经定义为宏 //#ifndef指令相反,用于测试一个标识符是否没有被定义为宏 #ifdef DEBUG printf("ok~"); #endif //严格来说,可以不需要#ifdef指令,用 #if defined(DEBUG)可以替代 #ifndef DEBUG //等价于 #if !defined DEBUG //#elif 和 #else指令 // #elif 常量表达式 #define WIN #if defined(WIN) printf("win..."); #elif defined(LINUX) printf("linux"); #elif defined(MAC) printf("mac"); #else printf("unkownd"); #endif
使用条件编译的场景:
1.编写在多态机器或多重操作系统之间可移植的程序
2.编写可以用不同的编译器编译的程序
3.为宏提供默认定义
4.临时屏蔽包含注释的代码
其他指令
#error #line #pragma //_Pragma运算符(C99) //__func__标识符(C99) //空的宏参数(C99) //参数个数可变的宏(C99)
参考: