预处理
# 1 预处理器preprocessor
在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。
# 1.1 预处理符号
符号 | 样例值 | 含义 |
---|---|---|
__FILE__ | "name.c" | 进行编译的源文件名 |
__LINE__ | 25 | 文件当前行的行号 |
__DATE__ | "Jan 21 2020" | 文件被编译的日期 |
__TIME__ | "18:04:30" | 文件被编译的时间 |
__STDC__ | 1 | 如果编译器遵循ANSI C,其值就为1,否则未定义 |
# 1.2 #define
- #define name stuff 每当有符号name出现在这条指令后面时,预处理器就会把它替换成stuff。利用 \ 进行分行。
- #undef 用于移除一个宏定义
#define SQUARE(x) x*x
a = 5;
printf("%d\n",SQUARE(a+1)); // 结果是11 a + 1 * a + 1 = 11
// 切记要加括号
#define SQUARE(x) (x)*(x) // 正确格式
#define MAX(a,b) ((a)>(b)?(a):(b))
# 2 宏运算符
# 2.1 #运算符
在函数式宏定义中,#运算符用于创建字符串,#运算符后面应该跟一个形参
#define STR(s) #s
#define STRINGIFY(x) #x
#define STRINGIFY_HELPER(x) STRINGIFY(x)
# 2.2 ##运算符
在宏定义中可以用##运算符把前后两个语句连接成一个语句,不能出现在宏定义的开头或末尾
# 3 宏函数
#define XYZ \
do{...} while(0)
使用do{...} while(0)把它包裹起来,成为一个独立的语法单元,里面是一些语句。
#define XX(name) \
case LogLevel::name: \
return #name; \
break;
XX(DEBUG);
#undef XX
变形为
#define XX(name) case LogLevel::name: return #name; break;
XX(DEBUG);
#undef XX
就非常清晰了。所以这个XX(DEBUG);预处理之后就是
case LogLevel::DEBUG:
return "DEBUG";
break;
# 4 extern "C"
让C代码可以在C++编译器编译运行,指定当前代码采用C进行编译
//__cplusplus是编译器提供好的宏,不是自定义的
#ifdef __cplusplus
extern "C"{
#endif // __cplusplus
//函数的声明
#ifdef __cplusplus
}
#endif // __cplusplus
# 5 条件编译
- 防止头文件被重复包含引用
#ifndef _SOMEFILE_H
#define _SOMEFILE_H
//需要声明的变量、函数
//宏定义
//结构体
#endif
#if constant-expression
statements
#endif
// constant-expression是常量表达式,它或者是字面值常量,或者是一个由#define定义的符号。如果它的值是非零值(真),那么statements部分就被正常编译,否则预处理器就安静地删除它们。
#if defined(symbol)
#ifdef symbol // 与上边等价
#if !defined(symbol)
#ifndef symbol
// 嵌套文件包含
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
// header file
#endif
- 软件裁剪 同样的C源代码,条件选项不同可以编译出不同的可执行程序。
#include <stdio.h>
#include <stdlib.h>
#define BIG 1
int main(void)
{
char str[20] = "C Language";
char C;
int i = 0;
while ((C = str[i++]) != '\0')
{
#if BIG
if (C >= 'a' && C <= 'z')
C = C - 32;
#else
if (C >= 'A'&& C <= 'Z')
C = C + 32;
#endif
printf("%c", C);
}
system("pause");
return 0;
}
# 6 #pragma
用于支持因编译器而异的特性。
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib")
// 这就相当于调用了Ws2_32.lib的库,我们也就不需要再工程中添加库了。
# 7 问题
在文件header1.h中:
#ifndef _HEADER1_H
#define _HEADER1_H
#include "header2.h"
其他声明
#endif
在文件header2.h中:
#ifndef _HEADER2_H
#define _HEADER2_H
#include "header1.h"
其他声明
#endif
上述这段代码没有问题,添加头声明只会编译一次。在#include 的位置直接用另一文件的内容进行替代。