C 语言第18讲:预处理详解

预处理是 C 语言编译前的文本替换与条件裁剪阶段,不做语法检查,却是大型项目、跨平台、底层开发必不可少的核心技能。本文把所有高频考点、工程用法一次性整理清楚,新手也能快速掌握。


📑 文章目录

  1. 预定义符号(内置宏)
  2. #define 定义常量
  3. #define 定义宏(带参宏)
  4. 带有副作用的宏参数
  5. 宏替换的规则
  6. 宏与函数的详细对比

字符串化 与 ## 记号粘合

  1. 命名约定(宏 / 函数区分)
  2. #undef 取消宏定义
  3. 命令行定义宏
  4. 条件编译(调试 / 跨平台必备)
  5. 头文件的包含与重复包含防护
  6. 其他预处理指令
  7. 面试高频考点总结

1. 预定义符号(内置宏)

C 语言自带一批预定义符号,预处理阶段直接展开,可直接使用

复制代码
__FILE__    // 正在编译的源文件名
__LINE__    // 当前行号
__DATE__    // 编译日期(月 日 年)
__TIME__    // 编译时间(时:分:秒)
__STDC__    // 遵循ANSI C则为1,否则未定义

使用示例:

复制代码
printf("文件:%s\n行号:%d\n", __FILE__, __LINE__);

2. #define 定义常量

语法:

复制代码
#define 名字 替换文本

示例:

复制代码
#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case

多行宏用续行符 \

复制代码
#define DEBUG_PRINT \
    printf("file:%s line:%d\n", __FILE__, __LINE__);

⚠️ **重点:宏定义尽量不要加分号 ;**否则替换后会多出语句,导致语法错误。


3. #define 定义宏(带参宏)

宏 = 带参数的文本替换。语法:

复制代码
#define 名字(参数列表) 替换内容

注意:左括号必须紧跟名字,不能有空格

宏的最大坑:优先级问题

错误写法:

复制代码
#define SQUARE(x) x*x
SQUARE(a+1);  → 替换成 a+1*a+1 结果错误

正确写法:每个参数加括号,整体加括号

复制代码
#define SQUARE(x) ((x)*(x))
#define DOUBLE(x) ((x)+(x))

4. 带有副作用的宏参数

当参数在宏里被多次使用时,自增 / 自减会引发灾难

示例:

复制代码
#define MAX(a,b) ((a)>(b)?(a):(b))
x=5,y=8;
z = MAX(x++, y++);

替换后:

复制代码
z = ((x++)>(y++)?(x++):(y++));

结果:x=6 y=10 z=9

结论:不要给宏传 x++、y-- 这类带副作用的参数!


5. 宏替换的规则

  1. 先检查参数是否包含其他宏,有则先替换
  2. 替换文本插入原位置
  3. 再次扫描结果,继续替换(直到无可替换)
  4. 不能递归
  5. 字符串常量中的内容不替换

6. 宏与函数对比(面试必考)

表格

对比点 函数
代码长度 每次都展开,会膨胀 只存一份,调用执行
执行速度 快(无调用开销) 稍慢(调用 + 返回)
优先级 易出错,必须加括号 安全可控
副作用参数 危险 安全
参数类型 无类型限制(通用) 类型严格限定
调试 不可调试 可逐行调试
递归 不能 可以

宏能做到函数做不到的事:把类型当参数传

c

运行

复制代码
#define MALLOC(num, type) (type*)malloc((num)*sizeof(type))

7. # 字符串化 与 ## 记号粘合

7.1 #:把参数变成字符串

复制代码
#define PRINT(n) printf("值为:" #n " = %d\n", n)
PRINT(a);
// 替换后:printf("值为:" "a" " = %d\n", a);

7.2 ##:把两个符号粘合成一个标识符

复制代码
#define GEN_MAX(type) \
type type##_max(type x,type y){ return x>y?x:y; }

GEN_MAX(int);   // 生成 int_max
GEN_MAX(float); // 生成 float_max

8. 命名约定

  • 宏名全部大写
  • 函数名不要全大写便于一眼区分,减少错误。

9. #undef 取消宏定义

复制代码
#undef 宏名

用于删除已有宏,方便重新定义。


10. 命令行定义宏

编译时指定宏,适合多版本编译:

bash

运行

复制代码
gcc -D ARRAY_SIZE=10 test.c

代码中直接用:

复制代码
int arr[ARRAY_SIZE];

11. 条件编译(调试 / 跨平台神器)

按条件保留 / 删除代码,常用指令:

复制代码
#if         // 条件为真编译
#ifdef      // 定义过则编译
#ifndef     // 没定义则编译
#elif
#else
#endif

示例:调试日志开关

复制代码
#define DEBUG 1
#if DEBUG
    printf("调试信息\n");
#endif

12. 头文件包含与重复包含防护

12.1 两种包含方式

  • #include "file.h" 先找当前目录,再找库路径
  • #include <file.h> 直接找库路径

12.2 重复包含问题

多次 #include 会导致内容重复、编译变慢、冲突报错。

解决方案两种

  1. 条件编译(最通用)

    #ifndef TEST_H
    #define TEST_H
    // 内容
    #endif

  2. #pragma once(编译器支持)

    #pragma once


13. 其他预处理指令

  • #error 编译报错
  • #pragma 编译选项设置
  • #line 重置行号

14. 面试高频考点

  1. 预处理主要做什么?
  2. 宏和函数的区别?
  3. 写宏为什么必须加括号?
  4. ### 的作用?
  5. 条件编译用途?
  6. 头文件重复包含如何解决?
  7. #include <>"" 区别?
  8. 宏参数带副作用会怎样?

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,后续会持续更新 C 语言干货~

相关推荐
zhouwy1133 小时前
C语言核心知识点详解
c语言
APIshop3 小时前
Java 调用阿里巴巴商品详情接口实战指南:完整流程与代码实现
java·开发语言
Bluetooth7303 小时前
c语言(选择与循环)程序与算法
c语言
努力努力再努力wz3 小时前
【Qt 入门系列】从应用场景到开发环境:建立对 Qt 的第一层认知
c语言·开发语言·数据库·c++·b树·qt·缓存
无限进步_3 小时前
【C++】红黑树完全解析:从概念到插入与平衡维护
java·c语言·开发语言·数据结构·c++·后端·算法
加勒比海带663 小时前
人工智能前沿——「试问当前国外AI大模型哪家强?」
大数据·开发语言·图像处理·人工智能
雪度娃娃3 小时前
Effective Modern C++——auto
开发语言·c++
50万马克的面包4 小时前
C语言数据在内存中的存储(后续会持续优化)
c语言
无限进步_4 小时前
简单聊聊 C++ 中的 unordered_map 和 unordered_set
c语言·开发语言·数据结构·c++·windows·哈希算法·散列表