知识点#
8.1 宏定义#
- @ 宏定义图
- ![[白板/P21 宏定义.canvas|P21 宏定义]]
8.1.1 不带参数的宏定义#
[!tip] 不带参数的宏定义
- @ 宏定义: 用一个指定的宏名来代表一串内容
- 一般形式:
#define 宏名 被替换的内容- 预处理时将宏名替换成指定内容的过程称为==宏展开==(宏替换)
#define PI 3.1415926 // 末尾没有分号
int main() {
float ftmp;
ftmp = 2 * PI; // 宏展开后: ftmp = 2 * 3.1415926;
printf("ftmp = %f\n", ftmp); // ftmp: 6.283185
return 0;
}
不带参数宏定义要点#
- ! 宏名一般用大写字母表示(习惯约定)
- ! 宏定义行末不加 分号,加了会连分号一起替换
- ! 宏定义不是 C 语言语句,不做语法检查
- !
#define出现在函数外面,有效范围从#define之后到本源程序文件结束 - ! 不能跨文件使用,跨文件需在另一个文件中重新定义或放到公共
.h头文件中
// 错误:加了分号
#define PI 3.1415926;
ftmp = 2 * PI; // 替换为: ftmp = 2 * 3.1415926;; 还好
ftmp = PI * 2; // 替换为: ftmp = 3.1415926; * 2; 语法错误!
#undef 终止宏定义#
#define PI 3.1415926
int main() {
float ftmp;
ftmp = PI * 2; // 正常使用
return 0;
}
#undef PI // 终止 PI 的作用域
void func1() {
float ftmp;
ftmp = PI * 2; // 报错!PI 已被 #undef 终止
}
宏定义的嵌套引用#
#define PI 3.1415926
#define DPI 2 * PI // 引用已定义的宏 PI
#define DPICPI PI * PI // 引用已定义的宏 PI
// 宏展开后:
// DPI → 2 * 3.1415926
// DPICPI → 3.1415926 * 3.1415926
- ! 字符串内的字符即使与宏名相同,也不进行替换
char stmp[100] = "DPICPI"; // 这里的 DPICPI 不会被替换
printf("stmp = %s\n", stmp); // stmp = DPICPI
8.1.2 带参数的宏定义#
[!tip] 带参数的宏定义
- 一般形式:
#define 宏名(参数表) 被替换的内容- 不仅进行内容替换,还要进行参数替换
#define S(a, b) a * b
int Area = S(3, 2); // 宏展开: int Area = 3 * 2;
#define PI 3.1415926
#define S(r) PI * r * r
float area;
area = S(3.6); // 宏展开: area = 3.1415926 * 3.6 * 3.6;
带参数宏定义的陷阱#
[!warning] 形参必须加括号!
- !
S(1+5)如果定义为#define S(r) PI * r * r,展开后变成3.1415926 * 1+5 * 1+5,结果错误- ! 正确写法:
#define S(r) PI * (r) * (r),展开后变成3.1415926 * (1+5) * (1+5)
- ! 宏名和带参数的括号之间不能加空格,否则空格后内容都作为替换内容
// 错误:宏名和括号之间有空格
#define S (r) PI * (r) * (r)
// S 变成不带参数的宏,代表 "(r) PI * (r) * (r)"
带参数宏 vs 函数#
| 对比项 | 带参数宏 | 函数 |
|---|---|---|
| 处理时机 | 编译阶段(预处理时展开) | 程序运行阶段 |
| 参数处理 | 简单内容替换,不求值 | 先求实参值,再传给形参 |
| 内存分配 | 不分配内存 | 调用时分配临时内存 |
| 参数类型 | 无类型概念,只是符号 | 有类型检查 |
| 代码长度 | 每次展开代码增多 | 不增加源代码长度 |
| 执行速度 | 只占编译时间,不占运行时间 | 占运行时间 |
复杂宏定义范例#
// 求 x 和 y 的最大值
#define MAX(x, y) (x) > (y) ? (x) : (y)
int result = MAX(3, 4);
printf("result = %d\n", result); // result = 4
// 多行宏定义(用 \ 续行)
#define PRINTF(str) do { \
printf(str); \
} while(0)
8.2 文件包含和条件编译#
- @ 文件包含与条件编译图
- ![[白板/P22 文件包含与条件编译.canvas|P22 文件包含与条件编译]]
8.2.1 文件包含#
[!tip] 文件包含
- @ 文件包含: 一个文件将另一个文件的全部内容包含进来
- 一般形式:
#include "文件名"
文件包含要点#
- ! 一条
#include命令只能包含一个文件 - !
#include可以嵌套(.h文件内可以再#include其他.h文件) - ! 修改
.h文件后,所有包含该.h的源文件都需要重新编译
"" vs <> 的区别#
| 形式 | 查找顺序 | 用途 |
|---|---|---|
#include <文件名> | 只在系统目录查找 | 系统提供的头文件,如 <stdio.h> |
#include "文件名" | 先当前目录,再系统目录 | 自己写的头文件 |
// head.h 文件内容
#define PI 3.1415926
// MyProject.cpp 文件
#include "head.h" // 包含 head.h 的内容到当前文件
int main() {
float ftmp;
ftmp = PI * 2;
printf("ftmp = %f\n", ftmp); // ftmp = 6.283185
return 0;
}
8.2.2 条件编译#
[!tip] 条件编译
- @ 条件编译: 对一部分内容指定编译条件,满足条件才编译
- ! 条件编译可以减少目标程序长度
- ! 条件编译常用于跨平台代码
形式 1: #ifdef#
#ifdef 标识符
程序段1 // 标识符被定义过时编译
#else
程序段2 // 标识符未被定义时编译
#endif
#define DEBUG 1 // 不想调试时注释掉这行
#ifdef DEBUG
printf("输出一些变量信息作为调试信息\n");
#endif
形式 2: #ifndef#
#ifndef 标识符
程序段1 // 标识符未被定义时编译
#else
程序段2 // 标识符被定义过时编译
#endif
#define RELEASE 1 // 定义 RELEASE
#ifndef RELEASE
printf("输出一些变量信息作为调试信息"); // 未定义 RELEASE 时才编译
#endif
形式 3: #if#
#if 表达式
程序段1 // 表达式为真(非0)时编译
#else
程序段2 // 表达式为假时编译
#endif
#elif 扩展形式#
#if 表达式1
程序段1
#elif 表达式2
程序段2
#else
程序段3
#endif
跨平台代码范例#
#if _WIN32 // Windows 平台(系统自动定义该宏)
// Windows 专用函数
// WaitForSingleObject(...);
printf("当前是Windows平台\n");
#elif __linux__ // Linux 平台(系统自动定义该宏)
// Linux 专用函数
// epoll_create(...);
printf("当前是Linux平台\n");
#else
// 其他平台的处理代码
#endif
条件编译的好处#
- ! 减少目标程序长度(不满足条件的代码不编译)
- ! 增加程序可移植性(跨平台代码无需修改即可在不同平台编译)

