当前位置: 首页 > news >正文

C语言之预处理和宏

 

(注:本文所讲为C语言生一些基本语法,其中主体为C语言,部分中采用了c++的一些语法。)

一.预定义符号

以下是C语言核心预定义符号的其中5个:
 
1.  __FILE__ :表示当前正在编译的源文件名称,是字符串类型,包含文件的完整路径 。常用于记录错误或调试信息,明确问题出现的文件。例如 printf("错误发生在文件:%s\n", __FILE__);  。


2.  __LINE__ :代表当前源代码所在的行号,为整型。配合 __FILE__ 能精准定位代码位置,如 fprintf(stderr, "错误在文件 %s 的第 %d 行\n", __FILE__, __LINE__);  。


3.  __DATE__ :字符串类型,格式为 "MMM DD YYYY"  ,表示源文件被编译的日期 。可用于标记代码版本或记录编译时间相关信息。


4.  __TIME__ :字符串类型,格式为 "HH:MM:SS"  ,代表源文件被编译的具体时间 。


5.  __STDC__ :整型,如果编译器遵循ANSI C标准,其值为1,否则未定义 。用于检测编译器对标准C的遵循情况,方便进行条件编译。
 

以上代码中,通过 cout  输出了 __FILE__  (源文件名)、 __LINE__  (当前行号)、 __DATE__  (编译日期)、 __TIME__   (编译时间)这些预定义宏。调试控制台展示对应的输出结果,直观呈现这些宏在程序调试和信息记录方面的作用。

二.#define

1.#define定义常量

基本格式:#define name stuff

 以上代码在VS2022环境中编写。代码首先通过宏定义 PI  表示圆周率近似值,以及用 do_forever  定义了一个死循环结构。在主函数里,先输出 PI  的值,接着进入死循环。循环内变量 i  自增,当 i  等于 5  时输出 i  的值并跳出循环。这展示了宏定义在简化代码和构建特定逻辑结构上的应用,以及循环和条件判断语句的基本用法。

注:在用宏定义标识符时,不要再最后加上引号

原因:

1. 语法错误风险

如果宏定义时不小心加了分号,如 #define PI 3.14159; ,在使用宏的地方,替换后会引入多余分号,可能导致语法错误。

2. 逻辑错误隐患

在宏定义函数 - like宏(带参数的宏)时,加分号问题更严重。

 

2.#define定义函数

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或定义宏。
下面是宏的申明方式:
1 #define  name( parament-list )  stuff

不过宏定义有一些问题需要注意,请查看以下代码

 以上是两段代码编写与运行结果。代码中包含头文件引入、命名空间使用,以及宏定义 ADD  和 SQUARE  。主函数里定义变量 x  并调用 SQUARE  宏计算表达式值。上方截图中 SQUARE  宏定义存在缺陷,未加括号,运行结果异常;下方截图修正宏定义,加上括号,运行输出正确结果 16  。体现宏定义中括号使用对运算优先级及结果的关键影响。

 

三.带有副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

 这段C++ 代码借助宏定义 MAX 来找出两数中的较大值。代码中对宏进行了两组测试:一组使用带有副作用的 a++ 和 b++ 作为参数,运行结果揭示了宏处理这类参数时会改变变量原本的值,导致结果与预期有偏差;另一组使用无副作用的 x + 1 和 y + 1 ,运行后变量值未受影响,结果正常。这鲜明地展示了宏在处理带副作用参数时的风险,警示我们编写代码时需谨慎对待宏定义中参数的副作用,以免引发程序逻辑错误。

 

四.宏的替换规则
 1.调用宏时参数检查
 在调用宏时,首先会对传入的参数进行检查,查看参数中是否包含其他由 #define 定义的符号。若包含,这些符号会首先被它们所代表的内容替换 。例如,若已定义 #define PI 3.14  , #define R 2  ,又有宏 #define CIRCUMFERENCE 2 * PI * R  ,在使用 CIRCUMFERENCE  时, PI  和 R  会先被替换成 3.14  和 2  。
 
2.文本替换
 完成参数中符号的替换后,预处理器会将宏定义中的替换文本插入到程序中调用宏的原始位置,此时宏定义中的参数名会被传入的实际参数值所替换 。比如 #define MAX(a, b) ((a) > (b)? (a) : (b))  ,当使用 MAX(3, 5)  时,会被替换为 ((3) > (5)? (3) : (5))   。
 
3.再次扫描
 插入替换文本后,预处理器会再次扫描结果文件,查看其中是否还包含由 #define 定义的符号。若存在,就重复上述替换过程,直至不再有可替换的 #define 定义符号 。
 
此外,还有一些注意事项:
 ①宏定义不能递归:宏参数和 #define 定义中虽可出现其他 #define 定义的符号,但宏定义自身不能直接或间接引用自身,否则会导致无限循环替换 。
②字符串常量不参与搜索:预处理器搜索 #define 定义的符号时,字符串常量的内容不会被搜索替换 。例如 #define HELLO "Hello"  ,程序中字符串  "Hello World"  里的 Hello  不会被当作宏来替换。

 

五.宏和函数对比


关键区别总结:

 1. 文本替换 vs 逻辑执行:宏是简单的文本替换,函数是运行时执行的指令。

2. 安全性与灵活性:函数更安全(类型检查、作用域),宏更灵活(可操作符号、条件编译等)。

3. 代码膨胀:宏可能引发多次替换导致代码膨胀,函数通常更节省空间。

 根据需求选择:性能敏感且简单的操作可用宏,复杂逻辑或需要安全性的场景优先用函数。

宏和函数的命名区别:

 

六.头文件的包含

1.头文件的被包含方式

"filename"与<filename>的区别

 标准库头文件不建议用 "" 包含的原因如下:

①查找效率低:用 "" 时编译器先在当前源文件目录找,没找到才去系统默认库路径找,而标准库头文件在系统默认库路径,这样会多一次不必要查找,浪费时间。

②可移植性问题:不同系统中标准库头文件位置相对固定且统一,用 "" 指定从当前目录找,在不同环境下可能找不到,导致代码在不同平台上移植时出错。 用 <> 直接去系统默认库路径找,可移植性更好。

 

2.嵌套文件的包含

在C语言中,嵌套文件包含指一个被包含的头文件中又包含另一个头文件 。比如文件a.h包含b.h ,b.h又包含c.h 。

 (1)注意事项

 ①防止重复包含:可能导致头文件内容被多次编译,出现重定义等错误。可使用头文件保护符(如 #ifndef 、 #define 、 #endif 组合 )或 #pragma once  避免。

②包含顺序:不合理顺序可能引发标识符未定义等问题,一般按使用依赖确定顺序。

 ③避免复杂嵌套:会让代码结构复杂、可读性变差,还影响编译速度。

 

(2)解决C语言中嵌套文件包含问题的方法如下:
 ①使用头文件保护符:在头文件中用 #ifndef 、 #define 、 #endif 防止重复定义。
 ②合理安排包含顺序:先包含被依赖的头文件,再包含依赖它的头文件。
③简化嵌套结构:尽量减少不必要的嵌套,使代码结构清晰。
④使用 #pragma once :在头文件开头添加 #pragma once ,保证头文件只被编译一次。

 

http://www.xdnf.cn/news/561961.html

相关文章:

  • SAP-ABAP:ABAP异常处理与SAP现代技术融合—— 面向云原生、微服务与低代码场景的创新实践
  • 云原生攻防4(Kubernetes基础补充)
  • word通配符表
  • Linux上conda环境安装完全手札
  • OpenHarmony外设驱动使用 (十),Sensor
  • 企业级爬虫开发全流程指南
  • elementUI 中el-date-picker和el-select的样式调整
  • CSS 文字样式全解析:从基础排版到视觉层次设计
  • spring-boot-starter-data-redis应用详解
  • C# AI(Trae工具+claude3.5-sonnet) 写前后端
  • maven快速上手
  • AI练习:混合圆
  • 【优秀三方库研读】在 quill 开源库 LogMarcos.h 中知识点汇总及讲解
  • CVE-2018-1270源码分析与漏洞复现(spring-messaging 表达式注入)
  • Flask 路由装饰器:从 URL 到视图函数的优雅映射
  • 使用Terraform创建azure databrick
  • 每日算法 -【Swift 算法】寻找字符串中最长回文子串(三种经典解法全解析)
  • 【工具教程】图片识别内容改名,图片指定区域识别重命名,批量识别单据扫描件批量改名,基于WPF和腾讯OCR的实现方案
  • HTML5 Video (视频) 深入解析
  • WPF···
  • [Java实战]Spring Boot整合MinIO:分布式文件存储与管理实战(三十)
  • Taro Error: chunk common [mini-css-extract-plugin]
  • 单片机设计_四轴飞行器(STM32)
  • apache http client连接池实现原理
  • 网络学习-利用reactor实现http请求(六)
  • K个一组链表翻转
  • 【大前端】使用NodeJs HTTP模块创建web服务器、SSE通讯
  • 运维web服务器
  • Java—— IO流 第二期
  • 怎么把cursor(Cursor/ollama)安装到指定路径