【C语言】预处理详解

在这里插入图片描述

前言
在上一篇博客中,我们了解了代码是如何执行的,简单介绍了编译中预处理步骤,在这篇博客中我们将详细了解预处理。


文章目录

  • 一、预定义符号
  • 二、#define定义
    • 2.1 定义常量
    • 2.2 定义宏
    • 2.3 创建代码片段
  • 三、#和##运算符
    • 3.1 字符串化操作符#
    • 3.2 连接操作符##
  • 四、宏和函数对比
  • 五、条件编译

一、预定义符号

预定义符号通常是指编程语言或编译器提供的一组特定的符号或宏,用于在代码中执行某些特定的功能或获取某些信息。

__FILE__ //返回当前源文件的文件名
__LINE__ //返回当前代码行的行号。
__DATE__ //返回编译的日期
__TIME__ //返回编译的时间
__func__ //或 __FUNCTION__(C++):返回当前函数的名称
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

一个使用场景

// 在错误消息中使用 __FILE__ 和 __LINE__ 打印出源代码文件名和行号
printf("Error in %s, line %d: Division by zero\n", __FILE__, __LINE__);       

二、#define定义

#define 是一个预处理指令,用于在C、C++和其他一些编程语言中创建宏或定义符号。它在编译前执行文本替换,将指定的符号替换为其对应的文本。

2.1 定义常量

//定义整数常量
#define MAX_VALUE 100//定义字符串常量
#define WELCOME_MESSAGE  "Hello, World!"

2.2 定义宏

宏是一种可以接受参数并展开为一段代码的定义。例如,你可以创建一个用于计算平方的宏。

#define SQUARE(x) ((x) * (x))int main(){int a = 5;printf("%d",SQUARE(a))://SQUARE(a)会被替换成((a) * (a)),结果是25
}

需要注意的是 \color{#FF0000}{需要注意的是} 需要注意的是 ,((x) * (x))内部不加括号会计算错误
例如:

#define SQUARE(x) (x * x)
int main(){int a = 5;printf("%d",SQUARE(a+1))://SQUARE(a)会被替换成(a+1*a+1),结果是11
}

同样的,((x) * (x))外部不加括号可能也会计算错误
举例:

#define DOUBLE(x) (x) + (x)
int main(){int a = 5;printf("%d",10*DOUBLE(a))://DOUBLE(a)会被替换成 10*(a)+(a),结果是55
}

因此,在定义宏时需格外注意。


此外, 副作用的宏参数 \color{#FF0000}{副作用的宏参数 } 副作用的宏参数 也需要格外注意

副作用的宏参数指的是在宏展开时,宏参数在宏中多次出现,或者在宏中进行了多次计算,特别是当参数包含函数调用、递增/递减操作或其他会改变参数状态的操作时,可能导致意外的行为或错误结果

例如下面这个例子

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )int main() {int x = 5;int y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n", x, y, z);//输出结果是x=6 y=10 z=9return 0;
}

在( (x++) > (y++) ? (x++) : (y++) )中进行了两次y的自增,一次的x自增,但是是后缀自增,所以在y第二次自增前返回给了z,所以z是9。


2.3 创建代码片段

宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

#define PRINT_MESSAGE(msg) printf("Message: %s\n", msg)//如果定义的代码过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ ) 

三、#和##运算符

3.1 字符串化操作符#

# 可以用于将宏参数转换为字符串,称为字符串化。

#define STRINGIZE(x) #x
int main() {int a = 5;printf("将%d转换为字符%s",a,STRINGIZE(5));return 0;
}

在这里插入图片描述


3.2 连接操作符##

## 可以用于将两个宏参数连接成一个标识符,称为连接操作符(记号粘合)。

例如:

#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \return (x>y?x:y); \
}

这样我们就可以定义根据返回类型来命名不同的函数

GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main(){//调⽤函数int m = int_max(2, 3);printf("%d\n", m);float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);return 0;
}

四、宏和函数对比

宏和可以实现简单函数的功能,对于实现相对简单的功能,宏更具有优势。但宏并不能取代函数。
以下是宏和函数的对比:

-#define定义宏函数
编译时 vs. 运行时宏是在编译时执行的文本替换操作。宏展开在编译阶段,不会引入运行时开销函数是在运行时执行的,需要在每次调用时进入和退出函数,具有运行时开销
类型安全宏是文本替换,没有类型检查。它们不会执行参数类型检查,可能引入类型错误函数具有明确的参数和返回值类型,编译器会执行类型检查,可以捕获类型错误
可读性和维护性宏可以导致代码变得难以阅读,因为它们是文本替换,展开后的代码可能很复杂函数具有明确的名称,参数和返回值,有助于代码的可读性和可维护性
代码复用宏可以在多个地方重复使用,但它们通常不能返回值,难以实现复杂的逻辑函数可以在多个地方调用,并且可以返回值,可以实现更复杂的逻辑
副作用宏可以包含副作用函数内的副作用通常较少
调试调试宏时,你只能查看宏在源代码中的展开,不容易进行调试函数是独立的代码单元,可以更容易进行单步调试和查看函数的堆栈跟踪

五、条件编译

在编译⼀个程序的时候我们如果不想编译⼀条语句(⼀组语句),除了将其注释掉,还可以使用条件编译。
条件编译是一种在C和C++中根据条件选择性包含或排除代码的技术。它使用预处理器指令来控制编译过程中哪些代码块应该被编译进最终的可执行程序。

条件编译使用以下预处理器指令来实现:

  • #ifdef 和 #ifndef:检查是否已定义了某个符号。
  • #if:执行条件判断,通常与预定义符号一起使用。
  • #else:在条件不成立时执行一段代码。
  • #elif:用于多个条件的情况,类似于 else if。
  • #endif:结束条件编译块。

例如:

#include <stdio.h>
#define DEBUG
int main() {int x = 10;#ifdef DEBUG //检查是否已定义了某个符号printf("Debugging information: x = %d\n", x);
#else //在条件不成立时执行一段代码printf("Release information\n");
#endif //结束条件编译块return 0;
}
//我们已经定义了DEBUG,所以会执行语句printf("Debugging information: x = %d\n", x);

在这里插入图片描述


在这里插入图片描述
如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/103350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python关联分析实践学习笔记

曾经有个沃尔玛超市&#xff0c;它将啤酒与尿布这样两个奇怪的东西放在一起进行销售&#xff0c;并且最终让啤酒与尿布这两个看起来没有关联的东西的销量双双增加。 我们关注的是在这样的场景下&#xff0c;如何找出物品之间的关联规则。接下来就来介绍下如何使用Apriori算法&…

RabbitMQ与springboot整合

1、基本概念 Server&#xff1a;接收客户端的连接&#xff0c;实现AMQP实体服务&#xff1b;Connection&#xff1a;连接&#xff0c;应用程序与Server的网络连接&#xff0c;TCP连接&#xff1b;Channel&#xff1a;信道&#xff0c;消息读写等操作在信道中进行。客户端可以建…

读书笔记-《ON JAVA 中文版》-摘要26[第二十三章 注解]

文章目录 第二十三章 注解1. 基本语法1.1 基本语法1.2 定义注解1.3 元注解 2. 编写注解处理器2.1 编写注解处理器2.2 注解元素2.3 默认值限制 3. 使用javac处理注解4. 基于注解的单元测试5. 本章小结 第二十三章 注解 注解&#xff08;也被称为元数据&#xff09;为我们在代码…

【Overload游戏引擎细节分析】从视图投影矩阵提取视锥体及overload对视锥体的封装

overoad代码中包含一段有意思的代码&#xff0c;可以从视图投影矩阵逆推出摄像机的视锥体&#xff0c;本文来分析一下原理 一、平面的方程 视锥体是用平面来表示的&#xff0c;所以先看看平面的数学表达。 平面方程可以由其法线N&#xff08;A, B, C&#xff09;和一个点Q(x0,…

让 Visual Studio 用上 ChatGPT

一、简介 Visual chatGPT Studio 是 Visual Studio 的一个免费扩展&#xff0c;它直接在 IDE 中添加了 chatGPT 功能。它允许用户以可以根据菜单功能的方式使用 chatGPT。 二、功能介绍 该扩展提供了一组使用 ChatGPT 命令&#xff0c;可以在编辑器中选择你需要处理的代码或…

k8s修改集群IP--不重置集群

正常在用集群想要更换ip master 节点ip192.168.10.138 改为192.168.10.148 node1节点ip192.168.10.139 改为192.168.10.149 node2节点ip192.168.10.140 改为192.168.10.150 master 节点 1)执行脚本1233.sh 1233.sh 内容如下&#xff1a; # master 节点 export oldip1192.168.…

可完全替代FTP的文件传输工具大集合

在当今的信息时代&#xff0c;文件传输是我们日常工作和生活中不可或缺的一项功能。无论是企业内部还是与外部合作伙伴之间&#xff0c;我们都需要频繁地进行各种类型和大小的文件的交换和共享。然而&#xff0c;传统的文件传输方式&#xff0c;如FTP、HTTP、CIFS等&#xff0c…

一键部署开源AI(人工智能对话模型)(支持显卡或CPU加内存运行)--ChatGLM-6B

一、基本介绍&#xff1a; 基于ChatGLM-6B 的快速安装服务&#xff0c;支持一键安装&#xff0c;无需任何服务配置和代码编写即可完成。 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数…

多尺度retinex图像去雾算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 clc; clear; close all; warning off; addpath(genpath(pwd)); rng(default)img_in im2doub…

微软 AR 眼镜新专利:包含热拔插电池

近日&#xff0c;微软在增强现实&#xff08;AR&#xff09;领域进行深入的研究&#xff0c;并申请了一项有关于“热插拔电池”的专利。该专利于2023年10月5日发布&#xff0c;描述了一款采用模块化设计的AR眼镜&#xff0c;其热插拔电池放置在镜腿部分&#xff0c;可以直接替代…

SpringBoot 如何使用 Sleuth 进行分布式跟踪

使用Spring Boot Sleuth进行分布式跟踪 在现代分布式应用程序中&#xff0c;跟踪请求和了解应用程序的性能是至关重要的。Spring Boot Sleuth是一个分布式跟踪解决方案&#xff0c;它可以帮助您在分布式系统中跟踪请求并分析性能问题。本文将介绍如何在Spring Boot应用程序中使…

Zabbix第二部分:基于Proxy分布式部署实现Web监控和Zabbix HA集群的搭建

代理和高可用 一、基于zabbix-proxy的分布式监控1.1 分布式监控的作用1.2 数据流向1.3 构成组件 二、部署zabbix代理服务器Step1 前置准备Step2 设置 zabbix 的下载源&#xff0c;安装 zabbix-proxyStep3 部署数据库并将zabbix相关文件导入Step4 修改zabbix-proxy的配置文件&am…

简述WPF中MVVM的设计思想

近年来&#xff0c;随着WPF在生产、制造、工控等领域应用越来越广泛&#xff0c;对WPF的开发需求也在逐渐增多&#xff0c;有很多人不断的从Web、WinForm开发转向了WPF开发。 WPF开发有很多新的概念及设计思想&#xff0c;如数据驱动、数据绑定、依赖属性、命令、控件模板、数…

智慧工地:助力数字建造、智慧建造、安全建造、绿色建造

智慧工地管理系统融合计算机技术、物联网、视频处理、大数据、云计算等&#xff0c;为工程项目管理提供先进的技术手段&#xff0c;构建施工现场智能监控系统&#xff0c;有效弥补传统监理中的缺陷&#xff0c;对人、机、料、法、环境的管理由原来的被动监督变成全方位的主动管…

AlphaPose Pytorch 代码详解(一):predict

前言 代码地址&#xff1a;AlphaPose-Pytorch版 本文以图像 1.jpg&#xff08;854x480&#xff09;为例对整个预测过程的各个细节进行解读并记录 python demo.py --indir examples/demo --outdir examples/res --save_img1. YOLO 1.1 图像预处理 cv2读取BGR图像 img [480,…

SyntaxError: invalid character ‘:‘ (U+FF1A)问题解决

问题&#xff1a; SyntaxError: invalid character &#xff1a; (UFF1A) 原因及解决方法&#xff1a; 冒号输入的格式不对&#xff0c;冒号的输入为中文&#xff0c;改成英文即可。

Design patterns--策略模式

设计模式之策略模式 笔者经常使用Modbus TCP和Modbus RTU通信协议&#xff0c;而两种的请求数据的格式不一样&#xff0c;故而采用策略模式来健壮整个工程项目。 代码示例 #ifndef MODBUS_H #define MODBUS_H #include <string>std::string convertToHex(unsigned char…

[GAMES101]透视投影变换矩阵中为什么需要改变z值

一、问题提出 在GAMES101-Lecture4 Transformation Matrices 一节中&#xff0c;闫老师介绍了正交投影和透视投影。 在讲透视投影变换矩阵 M p e r s p → o r t h o M_{persp→ortho} Mpersp→ortho​时&#xff0c;同学们对矩阵中的z分量是变化的还是不变的有很多争论。即下…

数据一致性分发

为什么要数据分发 微服务中&#xff0c;每个服务都有独立的数据源&#xff0c;这使得数据同步成为难题。 拉模式or推模式&#xff1f; 拉模式存在的问题 由于网络延迟&#xff0c;拉取的数据不一定是最新的 如果频繁向另一服务拉取数据&#xff0c;会给服务造成压力&#xf…

保护敏感数据的艺术:数据安全指南

多年来&#xff0c;工程和技术迅速转型&#xff0c;生成和处理了大量需要保护的数据&#xff0c;因为网络攻击和违规的风险很高。为了保护企业数据&#xff0c;组织必须采取主动的数据安全方法&#xff0c;了解保护数据的最佳实践&#xff0c;并使用必要的工具和平台来实现数据…