C 语言提供了三种预处理功能:宏定义、文件包含、条件编译。这些操作都在预处理阶段完成,因此他们只做替换,不进行计算。
1. 防止文件重复包含
#ifndef PROGRAM_H
#define PROGRAM_H
// .h file, usually declaration
#endif
现在使用新编译器可以如下替换:
#pragma once
2. 条件编译
#define TWO
//#define TWO 0 //此时为假
int main()
{
#ifdef ONE
printf("1\n");
#elif defined TWO
printf("2\n");
#else
printf("3\n");
#endif
}
// 输出 2
在编译的时候指定 -DONE 或 -DONE=1 就能够指定编译条件。
3. 特殊符号
以下是双下划线
符号 | 备注 |
---|---|
__FILE__ |
当前程序文件名的字符串 |
__LINE__ |
当前行号的整数 |
__STDC__ |
如果编译器遵循ANSI C标准,它就是个非零值 |
__DATE__ |
当前日期的字符串 |
__TIME__ |
当前时间的字符串 |
#error |
将使编译器显示一条错误信息,然后停止编译 |
#line |
指令改变LINE与FILE的内容,它们是在编译程序中预先定义的标识符 |
#pragma |
没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息 |
4. 无参宏定义
#define PI 3.141592653
- 宏名一般大写
- 宏定义允许嵌套
- 宏定义不存在类型
- 可以用
#undef PI
终止宏定义的作用域
无参数宏定义更推荐使用常量类型代替
const double PI = 3.141592653;
5. 带参宏定义
#define AREA(a,b) ((a)*(b))
- 记得要给参数加括号
#define AREA(a,b) a*b
AREA(2+3, 4) × 5
// 替换成 2 + 3 × 4 × 5
// 应该是 ((2×+3)×4) × 5
对于 decltype
而言, ()
有特殊含义,表示引用类型,因此当宏定义的结果为左值,并且宏定义外层加有括号,那么 decltype
结果就是引用类型。
#define INC(a) (++a)
int x = 1;
// decltype(INC(x)) y;
// y 是引用类型,必须初始化
decltype(INC(x)) y = x;
decltype(INC(x) + 0) z;
// z 是值类型
- 宏名和参数的括号间不能有空格
- 宏定义换行
#define _range(x,y) []{ \
std::vector<int> v((y-x)); \
std::iota(v.begin(), v.end(), x); \
return v; \
}()
- 宏定义展开会使源程序变长
6. 宏定义中 #
# 字符串化操作符
用于把宏定义中参数两端加上 引号""
#define STR(str) #str
STR(my#name) // "my#name"
STR( abc ) // 忽略参数名前后空格,"abc"
STR(abc def ) // 中间多个空格只保留一个 "abc def"
一般由任意字符都可以做形参,但以下情况会出错:
- STR())这样,编译器不会把“)”当成STR()的参数
- STR(,)同上,编译器不会把“,”当成STR的参数
- STR(A,B)如果实参过多,则编译器会把多余的参数舍去
- STR((A,B))会被解读为实参为:(A,B),而不是被解读为两个实参,第一个是(A第二个是B)
7. 宏定义中##
## 是记号粘贴操作符
普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。
#define A1(name, type) type name_##type##_type
#define A2(name, type) type name ##_##type##_type
A1(a1, int); // int name_int_type;
A2(a1, int); // int a1_int_type;
8. 宏支持可变参数
使用 VA_ARGS 把参数传递给宏
#define PRINT(...) printf(__VA_ARGS__)
// or
// #define PRINT(fmt, args...) printf(fmt, args)
PRINT("%d %c\n", 10, 'c');