ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C语言:预处理

2026/8/24 13:01:02 拓冰建站 浏览量
C语言:预处理 文章目录前言本文旨在系统性地介绍C语言中的预处理。一、预定义符号二、#define定义宏2.1、#define定义标识符无参宏2.2、宏定义中括号的使用2.3、带有副作用的宏参数2.4、宏替换的规则2.5、宏和函数的对比三、命名约定四、#undef五、条件编译六、头文件包含6.1、头文件被包含的方式6.2、嵌套文件包含前言本文旨在系统性地介绍C语言中的预处理。一、预定义符号预定义符号是编译器预先定义的宏可直接使用无需 #define。它们在预处理阶段被替换为对应的值。__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定义宏宏定义允许将参数替换到文本中其语法形式如下#definename(parament-list)stuffname宏名(parament-list)参数列表由逗号分隔的符号表stuff替换文本参数可以出现在其中注意事项参数列表的左括号必须紧挨宏名不能有空格。如果有空格参数列表被当成替换文本。2.1、#define定义标识符无参宏基本语法#definenamestuff代码示例#defineMAX1000#defineregregister// 为 register 关键字起简短别名#definedo_foreverfor(;;)// 用形象符号替换无限循环#defineCASEbreak;case// case 语句自动加 break当 stuff 过长时可以用反斜杠\续写符分行编写最后一行不加告诉预处理器下一行是当前行的延续在预处理阶段会合并成一行。如果没有续行符,预处理后宏定义只到第一行结束后面的内容被当作普通代码导致编译错误。代码示例#defineDEBUG_PRINTprintf(file:%s\tline:%d\t\date:%s\ttime:%s\n,\__FILE__,__LINE__,\__DATE__,__TIME__)末尾不建议加分号否则可能引发语法错误。代码示例#defineMAX1000;if(condition)maxMAX;// 替换后 → max 1000;;// 替换后if 和 else 之间变成两条语句导致编译错误。elsemax0;2.2、宏定义中括号的使用宏定义中给每个参数加上括号再给整个表达式加上括号可以避免因操作符优先级导致的意外结果。①参数未加括号宏是纯文本替换参数中的操作符优先级影响计算顺序#defineSQUARE(x)x*xSQUARE(a1)// 替换后 → a 1 * a 1// 实际计算a (1 * a) 1// 结果与预期不符解决办法给宏体的每个参数加上括号。#defineSQUARE(x)(x)*(x)SQUARE(a1)// 替换后 → (a 1) * (a 1)②整个宏体未加括号宏体本身没有括号导致外部运算符影响计算顺序#defineDOUBLE(x)(x)(x)10*DOUBLE(a)// 替换后 → 10 * (5) (5)// 实际计算10 * 5 5 55解决办法给整个宏体加上括号。#defineDOUBLE(x)((x)(x))10*DOUBLE(a)// 替换后 → 10 * ((5) (5)) 1002.3、带有副作用的宏参数宏参数在宏体中多次出现时带副作用的参数会被多次求值导致不可预测的结果。应避免在宏参数中使用 、-- 等操作或用函数替代宏。副作用是指表达式求值时永久改变变量值的效果。x1;// 无副作用x 不变x;// 有副作用x 被修改代码示例#includestdio.h#defineMAX(a,b)((a)(b)?(a):(b))intmain(){intx5;inty8;// 宏调用MAX(x, y)// 预处理后展开为((x) (y) ? (x) : (y))// 执行过程// ① 比较 (x) (y)即 5 8假此时 x6y9// ② 条件为假执行 (y)取 y 的当前值 9 赋给 z然后 y 变为 10// ③ 最终x6y10z9intzMAX(x,y);printf(x%d y%d z%d\n,x,y,z);return0;}2.4、宏替换的规则① 参数检查检查宏参数是否包含 #define 定义的符号如果有则先替换② 文本插入将替换文本插入到宏调用的位置参数名被对应的值替换③ 再次扫描对结果文件再次扫描检查是否还有 #define 定义的符号如果有则重复上述过程代码示例#definePI3.14#defineAREA(r)(PI*(r)*(r))doublesAREA(5);替换过程①检查参数 5没有 #define 定义的符号②插入替换文本(PI * (5) * (5))③再次扫描发现 PI 是 #define 定义的符号④替换 PI(3.14 * (5) * (5))。注意事项①宏不能递归宏不能调用自身②字符串内容不搜索字符串字面量中的内容不会被宏替换。代码示例#definePI3.14printf(PI %f,PI);// 宏被替换printf(PI);// 字符串中的 PI 不会被替换2.5、宏和函数的对比对比项宏Macro函数Function执行时机预处理阶段展开运行时调用执行效率高无调用开销低有调用和返回开销代码长度大型宏多次展开使程序膨胀代码只存一份类型检查无有调试无法调试可逐语句调试类型通用类型无关参数类型固定参数为类型可以如 MALLOC(10, int)不可以副作用风险参数多次求值参数只求值一次内存开销不占栈空间每次调用占用栈空间递归不可递归可递归操作符优先级易受邻近操作符影响安全参数先求值三、命名约定宏名全部大写函数名不要全部大写C语言本身没有区分目的是让代码可读性更强便于区分宏和函数。四、#undef#undef 指令用于移除一个已存在的宏定义。如果需要重新定义一个宏名或取消某个宏的作用可以先用 #undef 移除旧定义。语法形式#undefNAME代码示例#includestdio.h#defineMAX100// 此时 MAX 已定义#undefMAX// 移除 MAX 的宏定义// 此时 MAX 未定义不能再使用#ifdefMAXprintf(MAX 已定义\n);#elseprintf(MAX 未定义\n);#endif五、条件编译条件编译用于在预处理阶段根据条件决定哪些代码被保留、哪些被丢弃常用于调试信息控制、头文件保护和跨平台适配。常见条件编译指令①单分支条件编译#if常量表达式// 表达式为真时编译#endif#define__DEBUG__1#if__DEBUG__printf(调试模式\n);// 被编译#endif②多分支条件编译#defineVERSION2#ifVERSION1printf(版本1\n);#elifVERSION2printf(版本2\n);#elseprintf(其他版本\n);#endif③判断是否被定义写法含义#if defined(symbol)symbol 已定义时为真#ifdef symbol同上简写#if !defined(symbol)symbol 未定义时为真#ifndef symbol同上简写#defineDEBUG#ifdefDEBUGprintf(调试模式\n);// 被编译#endif#ifndefRELEASEprintf(非发布模式\n);// 不被编译#endif④嵌套条件编译#ifdefined(OS_UNIX)#ifdefOPTION1unix_version_option1();#endif#ifdefOPTION2unix_version_option2();#endif#elifdefined(OS_MSDOS)#ifdefOPTION2msdos_version_option2();#endif#endif六、头文件包含6.1、头文件被包含的方式包含方式查找路径适用场景#include xxx.h直接去标准系统路径查找库文件标准库、第三方库#include “xxx.h”先在当前源文件所在目录查找找不到再去标准路径查找本地头文件项目自定义库文件也可以用 “” 包含但是查找效率低不易区分是库还是本地文件。6.2、嵌套文件包含头文件被重复包含// test.c#includetest.h#includetest.h#includetest.h#includetest.h#includetest.h预处理后test.h 的内容会被拷贝 5 次导致代码膨胀和编译压力增加。解决方案1头文件守卫条件编译#ifndef__TEST_H__#define__TEST_H__// 头文件的内容voidtest();structStu{intid;charname[20];};#endif// __TEST_H__解决方案2#pragma once#pragmaonce// 头文件的内容voidtest();structStu{intid;charname[20];};