编译前奏:预处理全面梳理
个人主页 流年如夢专栏 《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例如#includestdio.hintmain(){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(51)会替换为51*5111结果并非36而是11正确示例#defineSQUARE(x)((x)*(x))四.带有副作用的宏参数副作用 -- 参数被多次求值例如#defineMAX(a,b)((a)(b)?(a):(b))intx5,y8;intzMAX(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)\{returnxy?x:y;}再使用GENERIC_MAX(int)GENERIC_MAX(float)最后生成intint_max(intx,inty);floatfloat_max(floatx,floaty);八.命名约定这个简单宏名全大写函数名不全大写这是为了便于区分避免误用九.#undef即取消宏定义取消宏定义#undefMAX用于重新定义宏十.命令行定义编译时指定宏不修改代码在gcc编译器里gcc-D ARRAY_SIZE10test.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判断循环结束逻辑错误 关注我们一路同行从入门到大师慢慢沉淀、稳步成长❤️ 点赞鼓励原创让优质内容被更多人看见⭐ 收藏收好核心知识点与实战技巧需要时随时查阅 评论分享你的疑问或踩坑经历一起交流避坑、共同进步