个人主页: 流年如夢
专栏: 《C语言》
文章目录
- 一.预定义符号
- 二.#define 定义常量
- 三.#define 定义宏
- 四.带有副作用的宏参数
- 五.宏替换的规则
- 六.宏和函数的对比
- 七.#和##运算
- 7.1# --> 字符串化
- 7.2## --> 记号粘合
- 八.命名约定
- 九.#undef(即取消宏定义)
- 十.命令行定义
- 十一.条件编译
- 十二.头文件的包含
- 12.1两种包含方式
- 12.2避免头文件重复包含
- 12.2.1通用方法
- 12.2.2个别编译器支持
- 十三.其他预处理指令
- 🎯总结
- ⚠️易错点
Ladies and gentlemen,本篇文章讲的是预处理全过程、#define 宏、#与##、条件编译、头文件包含等核心知识点;全程高能,不容错过!!!
前言 预处理是C程序编译前的文本替换与处理阶段,不生成可执行代码,只对源文件做文本层面的替换、展开、删除、包含等工作;掌握预处理,能写出更简洁、更易维护、更少BUG 的代码
一.预定义符号
C语言内置预处理符号,可以直接使用:
__FILE__--> 当前源文件名__LINE__--> 当前行号__DATE__--> 编译日期__TIME__--> 编译时间__STDC__--> 遵循 ANSI C 则为1
例如:
#include<stdio.h>intmain(){printf("当前文件:%s\n",__FILE__);printf("当前行号:%d\n",__LINE__);printf("编译日期:%s\n",__DATE__);printf("编译时间:%s\n",__TIME__);return0;}运行结果:
二.#define 定义常量
#defin的语法:
#definenamestuff例如:
#defineMAX1000#defineDEBUG_PRINTprintf("file:%s line:%d",__FILE__,__LINE__)注意❗:定义常量不要加分号;,当宏替换后会多出分号;
错误示例:
#defineMAX1000;//<--这里多了分号三.#define 定义宏
宏 = 带参数的文本替换
宏的语法:
#definename(parameter-list)stuff其中左括号必须紧跟宏名,不能有空格
错误示例:
#defineSQUARE(x)x*x🧐分析:调用SQUARE(5+1)会替换为5+1*5+1=11,结果并非36,而是11
正确示例:
#defineSQUARE(x)((x)*(x))四.带有副作用的宏参数
副作用 --> 参数被多次求值
例如:
#defineMAX(a,b)((a)>(b)?(a):(b))intx=5,y=8;intz=MAX(x++,y++);替换后变成👉:
z=((x++)>(y++)?(x++):(y++));运行结果:
五.宏替换的规则
- 先检查参数,替换其中已定义的宏
- 接着替换文本插入原位置
- 然后再次扫描,继续替换(宏不能递归)
👉需要注意的是字符串常量内的内容不被替换且宏不能递归定义
六.宏和函数的对比
| 对比 | 宏 | 函数 |
|---|---|---|
| 代码长度 | 每次调用都插入,代码变长 | 只一份,调用跳转 |
| 执行速度 | 快(无需调用) | 稍慢 |
| 优先级问题 | 易出错,必须加括号 | 无问题 |
| 副作用参数 | 危险,多次求值 | 安全,只求值一次 |
| 参数类型 | 无类型限制 | 类型严格 |
| 调试 | 不可调试 | 可逐行调试 |
| 递归 | 不可递归 | 可递归 |
👉宏的优势在于可以接收类型做参数,但函数做不到:
#defineMALLOC(num,type)(type*)malloc((num)*sizeof(type))七.#和##运算
7.1# --> 字符串化
用#把宏参数变成字符串:
#definePRINT(n)printf("the value of "#n" is %d\n",n)其中调用PRINT(a)替换为:
printf("the value of ""a"" is %d\n",a);7.2## --> 记号粘合
就是把两个符号合成一个标识符:
#defineGENERIC_MAX(type)\type type##_max(type x,type y)\{returnx>y?x:y;}再使用:
GENERIC_MAX(int)GENERIC_MAX(float)最后生成👉:
intint_max(intx,inty);floatfloat_max(floatx,floaty);八.命名约定
这个简单,宏名全大写,函数名不全大写(这是为了便于区分,避免误用)
九.#undef(即取消宏定义)
取消宏定义:
#undefMAX用于重新定义宏
十.命令行定义
编译时指定宏,不修改代码(在gcc编译器里)
gcc-D ARRAY_SIZE=10test.c在程序中(vs2022)可以直接使用:
intarr[ARRAY_SIZE];十一.条件编译
条件编译即按条件决定是否编译某段代码,常用于调试、跨平台
常用的指令:
#if//-->条件为真则编译#elif//-->否则如果#else//-->否则#endif//-->结束#ifdefSYMBOL//-->已定义则编译#ifndefSYMBOL//-->未定义则编译例如(调试日志开关):
#define__DEBUG__1//这里1为真#if__DEBUG__printf("debug info\n");#endif十二.头文件的包含
12.1两种包含方式
#include "filename"--> 先找当前目录,再找标准库#include <filename>--> 直接找标准库路径
12.2避免头文件重复包含
12.2.1通用方法
#ifndef__TEST_H__#define__TEST_H__//内容#endif12.2.2个别编译器支持
#pragmaonce十三.其他预处理指令
#error--> 编译时报错#pragma--> 向编译器发送指令#line--> 重置行号
🎯总结
- 预处理是纯文本处理,不涉及语法、语义
- 宏是高效替换,必须全程加括号
- 宏参数有副作用(
++、--)会引发严重问题 #字符串化,##记号粘合- 条件编译用于跨平台、调试、裁剪代码
- 头文件必须加保护,避免重复包含
- 宏比函数快,但更危险、难调
⚠️易错点
- 宏定义不加括号,优先级出错
- 宏参数带副作用(
++、--)- 常量定义末尾多写分号
- 分不清
#include <>和""的区别- 忘记头文件保护,导致重复包含
- 用feof判断循环结束,逻辑错误
👀 关注我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞鼓励原创,让优质内容被更多人看见
⭐ 收藏收好核心知识点与实战技巧,需要时随时查阅
💬 评论分享你的疑问或踩坑经历,一起交流避坑、共同进步