C语言:预处理
2026/8/24 13:00:31 网站建设 项目流程

文章目录

  • 前言
    • 本文旨在系统性地介绍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,否则未定义

代码示例:

#include<stdio.h>intmain(){printf("文件:%s\n",__FILE__);printf("行号:%d\n",__LINE__);printf("编译日期:%s\n",__DATE__);printf("编译时间:%s\n",__TIME__);return0;}

二、#define定义宏

宏定义允许将参数替换到文本中,其语法形式如下:

#definename(parament-list)stuff

name:宏名
(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)max=MAX;// 替换后 → max = 1000;;// 替换后if 和 else 之间变成两条语句,导致编译错误。elsemax=0;

2.2、宏定义中括号的使用

宏定义中,给每个参数加上括号,再给整个表达式加上括号,可以避免因操作符优先级导致的意外结果。

①参数未加括号(宏是纯文本替换,参数中的操作符优先级影响计算顺序)

#defineSQUARE(x)x*xSQUARE(a+1)// 替换后 → a + 1 * a + 1// 实际计算:a + (1 * a) + 1// 结果与预期不符

解决办法:给宏体的每个参数加上括号。

#defineSQUARE(x)(x)*(x)SQUARE(a+1)// 替换后 → (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)) = 100

2.3、带有副作用的宏参数

宏参数在宏体中多次出现时,带副作用的参数会被多次求值,导致不可预测的结果。应避免在宏参数中使用 ++、-- 等操作,或用函数替代宏。

副作用是指表达式求值时永久改变变量值的效果。

x+1;// 无副作用(x 不变)x++;// 有副作用(x 被修改)

代码示例:

#include<stdio.h>#defineMAX(a,b)((a)>(b)?(a):(b))intmain(){intx=5;inty=8;// 宏调用:MAX(x++, y++)// 预处理后展开为:((x++) > (y++) ? (x++) : (y++))// 执行过程:// ① 比较 (x++) > (y++),即 5 > 8(假),此时 x=6,y=9// ② 条件为假,执行 (y++),取 y 的当前值 9 赋给 z,然后 y 变为 10// ③ 最终:x=6,y=10,z=9intz=MAX(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))doubles=AREA(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

代码示例:

#include<stdio.h>#defineMAX100// 此时 MAX 已定义#undefMAX// 移除 MAX 的宏定义// 此时 MAX 未定义,不能再使用#ifdefMAXprintf("MAX 已定义\n");#elseprintf("MAX 未定义\n");#endif

五、条件编译

条件编译用于在预处理阶段根据条件决定哪些代码被保留、哪些被丢弃,常用于调试信息控制、头文件保护和跨平台适配。

常见条件编译指令:
①单分支条件编译

#if常量表达式// 表达式为真时编译#endif
#define__DEBUG__1#if__DEBUG__printf("调试模式\n");// 被编译#endif

②多分支条件编译

#defineVERSION2#ifVERSION==1printf("版本1\n");#elifVERSION==2printf("版本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#include"test.h"#include"test.h"#include"test.h"#include"test.h"#include"test.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];};

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询