跳过正文
P7 编译预处理
  1. S/
  2. C/C++/
  3. C/
  4. C语言程序设计/

P7 编译预处理

Li
作者
Li
往前走,别回头!
目录
C 语言程序设计笔记 - 这篇文章属于一个选集。

知识点
#

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

条件编译的好处
#

  • ! 减少目标程序长度(不满足条件的代码不编译)
  • ! 增加程序可移植性(跨平台代码无需修改即可在不同平台编译)
Reply by Email
C 语言程序设计笔记 - 这篇文章属于一个选集。

相关文章