【C语言】预处理常见知识详解(宏详解)

news/2024/4/28 0:43:30/文章来源:https://blog.csdn.net/2301_77954967/article/details/137122271

文章目录

  • 1、预定义符号
  • 2、define
    • 2.1 define 定义常量
    • 2.2 define 定义宏
  • 3、#和##
    • 3.1 **#**
    • 3.2 **##**
  • 4、条件编译(开关)


1、预定义符号

在C语言中内置了一些预定义符号,可以直接使用,这些符号实在预处理期间处理的,并且这些符号都是C语言ANSIC里收集的

在这里插入图片描述

但是在笔者的VS2022里不完全支持ANSIC的所有预定义符号

在这里插入图片描述

这里把这里的预定义处理STDC都打印了一遍,证明都可以实现,但是当我们打印STDC时会出现
在这里插入图片描述
由此可见随着编译器版本的升级有些C语言内置预定义会被忽视掉

2、define

2.1 define 定义常量

基本语法

#define name stuff

以下举例

#define MAX 1000//
#define reg register//为 register这个关键字,创建一个简短的名字/用更形象的符号来替换一种实现
#define do_forever for(;;)//定义函数,但是这个会造成死循环
#define CASE break;case//在写case语句的时候自动把 break写上。
#define DEBUG PRINT printf("file:%s\tline:%d\t\date:%s\ttime:%s\n",\__FILE__,-__LINE__,\__DATE__,__TIME__)
//如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符),
//相当于下一行链接在‘\’处

思考:需要在定义后加上“;”吗?
例如:

#define MAX 1000
#define MAX 1000;

建议是不加上,可能会导致原来语句顺序发生错乱
假如上上面的MAX 1000最后加上了一个分号就会导致以下的运行情况

if(condition)max = MAX;;
elsemaX = 0

这种情况下,相当于if后面跟了两条语句,但是如果没有大括号的情况下,是不允许有多条语句的,所以这会导致if语句加执行语句max = MAX;,后跟了一条空语句,这会导致else前面断开,产生语法错误。

2.2 define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

申明方式

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

  • 注意1:parament-list 的左括号必须紧贴在name的右边,一旦有空格在中间,就会让宏判定 ( parament-list ) 属于后面的 stuff

  • 注意2:宏一旦运行后会直接替换 parament-list 的内容到stuff里, stuff 及 parament-list 里的计算符的优先级就可能会导致计算顺序发生变化,
    所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

例如,现在有这样一个宏

#define double(x) x*x

没有注意用括号区分优先级的话,当 x 为 x+1 时,
这个宏的运算的顺序就是 x+1*x+1,即 x + x +1,最终导致结果发生较大偏差
但如果我们定义宏为

#define double(x) (x)*(x)

就不会在导致这种情况

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

以下举例

#define MAX(X,Y) ((X)>(Y)?(X):(Y))int main()
{int a = 3;int b = 5;int m = MAX(a++, b++);//相当于替换成//int m = MAX(a++,b++) ((a++)>(b++)?(a++):(b++))//a++和b++各会执行两次printf("m = %d\n", m);//6printf("a = %d\n", a);//4printf("b = %d\n", b);//7return 0;
}

宏替换规则总结
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

宏和函数的对比
在这里插入图片描述

命名规则
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者那我们平时的一个习惯是:

把宏名全部大写
函数名不要全部大写

其中内置宏 offsetof 比较特殊,它是红,但是全部小写,它的作用是计算结构体成员相对结构体起始位置的偏移量

#undef
用来移除一个宏定义

例如下图
在这里插入图片描述
原来没有移除 MAX 的时候,是可以打印出来的,但是一移除后就显示未定义标识符。


3、#和##

3.1 #

#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为”字符串化“。

例如:当我们有一个变量 int a = 10;的时候,我们想打印出:the value of ais 10
就可以写下面这个宏

#define PRINT(n) printf("the value of "#n " is %d", n);

当我们把a替换到宏的体内时,就出现了#a,而#a就是转换为"a”,时一个字符串代码就会被预处理为:

printf("the value of ""a" " is %d", a);

结果就是

the value of a is 10

3.2 ##

##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称为记号粘合
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
这里我们想想,写一个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。

比如,我们相要写下面这两个函数,但是要不断地写他们的类型名,这里我们可以利用宏

int int_max(int x, int y)
{return x > y ? x : y;
}
float float_max(float x, float y)
{return x > y ? x:y;
}

我们相要写上面这两个函数,但是要不断地写他们的类型名,这样太繁琐了,这里我们可以利用宏

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

其中 ## 的作用是链接type和后面的_max,否则,当type被输入后,type_max 就会被认为时一个,使得函数名一直是 type_max

定义好宏后,我们放入类型名进去,就会产生新的函数名,然后可以进行使用

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;
}

最终结果是

3
4.500000

4、条件编译(开关)

当我们在调试时需要写一些语句辅助,但是正式发布时可能是不带的,我们一遍在正式使用时会注释掉,但这仅适用于小型代码,一旦代码量达到一个恐怖速度,就可能会有缺漏,并造成巨大的工作量,所以就用到了部分条件编译,用作开关

例如

#include <stdio.h>
#define __debug__
int main()
{int i = 0;int arr[10] = { 0 };for (i = 0; i < 10; i++){arr[i] = i;
#ifdef __debug__printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__debug__}return 0;
}

这里的代码,中间那条输出就是调试时观察用的,我们在开头定义宏,一旦不需要用掉,只需要把宏去了,就不会运行这行代码,这种方法用在代码很长的时候很有用,用做开关。

除了这个 #ifdef 还有很多别的

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif2.多个分⽀的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif

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

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

相关文章

云蜜罐技术(德迅猎鹰)诞生

数字化程度高且高价值信息密集的行业&#xff0c;如金融、能源、互联网、政府、教育、医疗、军工等行业&#xff0c;面对日益规模化、专业化的网络攻击&#xff0c;渐渐不再满足于一味的防守加固。除了巩固防线之外&#xff0c;他们愈发看重主动出击、感知更大范围内的攻击&…

uniapp调用腾讯图形验证码,兼容h5、APP(安卓)

因项目要兼容安卓APP&#xff0c;所以使用webview做成了一个组件 新建hybrid文件夹&#xff0c;创建要在webview引入的html文件 <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"viewport"content"widt…

Jenkins拉取github项目相关问题

1.私有仓库问题 1.1如果你的仓库是私有的&#xff0c;21年起github就不支持账号密码的方式拉取代码了 那么就需要在github上面创建一个token (classic) 然后在Jenkins代码设置那里 然后应该就可以顺利打包了。 2.找不到pom&#xff08;多了一层文件夹&#xff09;问题 解…

Svg Flow Editor 原生svg流程图编辑器(四)

系列文章 Svg Flow Editor 原生svg流程图编辑器&#xff08;一&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;二&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;三&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;四&#xf…

边缘计算与云计算总结

一. EdgeGallery 简介 MEC场景下的EdgeGallery是让资源边缘化&#xff0c;实时完成移动网络边缘的业务处理&#xff0c;MEC场景下的EdgeGallery让开发者能更便捷地使用 5G 网络能力&#xff0c;让5G能力在边缘触手可及。 EdgeGallery是由华为、信通院、中国移动、中国联通、…

TheMoon 恶意软件短时间感染 6,000 台华硕路由器以获取代理服务

文章目录 针对华硕路由器Faceless代理服务预防措施 一种名为"TheMoon"的新变种恶意软件僵尸网络已经被发现正在侵入全球88个国家数千台过时的小型办公室与家庭办公室(SOHO)路由器以及物联网设备。 "TheMoon"与“Faceless”代理服务有关联&#xff0c;该服务…

Linux内核之最核心数据结构之一:struct file(三十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

ubuntu编译OpenCV and seetaFace2

opencv opencv-4.5.2 opencv_contrib-4.5.2 SeetaFace2 SeetaFace2-master https://github.com/seetafaceengine 指定安装目录&#xff0c;和OpenCV放一个目录下了 安装前 安装 安装后 Qt安装 Windows下 Linux下 报错1 原因&#xff1a; 报错…

Linux指令大全,第二篇(超详细版)

目录 9.1 链接文件的命令 10.1. 用户和权限 11.1 文件压缩和解压命令 12.1 文件操作命令补充 13.1 Vim编辑器的使用 14.1 Linux开关机命令 15.1 Shell Linux 程序设计&#xff08;未完持续更新~&#xff09; 16.1 以上运行效果图如下 9.1 链接文件的命令 9.1.1 …

ChatGPT与传统搜索引擎的区别:智能对话与关键词匹配的差异

引言 随着互联网的快速发展&#xff0c;信息的获取变得比以往任何时候都更加便捷。在数字化时代&#xff0c;人们对于获取准确、及时信息的需求愈发迫切。传统搜索引擎通过关键词匹配的方式为用户提供了大量的信息&#xff0c;然而&#xff0c;这种机械式的检索方式有时候并不…

鱼眼相机的测距流程及误差分析[像素坐标系到空间一点以及测距和误差分析]

由于最近在整理单目测距的内容&#xff0c;顺手也总结下鱼眼相机的测距流程和误差分析&#xff0c;如果有错误&#xff0c;还请不吝赐教。 参考链接: 鱼眼镜头的成像原理到畸变矫正&#xff08;完整版&#xff09; 相机模型总结&#xff08;针孔、鱼眼、全景&#xff09; 三维…

机器人是怎么计时的(通用定时器 - 时基单元)

目录 一&#xff0c;引言 二&#xff0c;机器人的“大脑” 三&#xff0c;时基单元介绍 1&#xff0c;定时器框图 2&#xff0c;时基单元 &#xff08;1&#xff09;预分频器 &#xff08;2&#xff09;CNT计数器 &#xff08;3&#xff09;自动重装载寄存器 四&#…

智慧管道物联网远程监控解决方案

智慧管道物联网远程监控解决方案 智慧管道物联网远程监控解决方案是近年来在智能化城市建设和工业4.0背景下&#xff0c;针对各类管道网络进行高效、安全、精准管理的前沿科技应用。它融合了物联网技术、大数据分析、云计算以及人工智能等多种先进技术手段&#xff0c;实现对管…

【MATLAB源码-第171期】基于matlab的布谷鸟优化算法(COA)无人机三维路径规划,输出做短路径图和适应度曲线

操作环境&#xff1a; MATLAB 2022a 1、算法描述 布谷鸟优化算法&#xff08;Cuckoo Optimization Algorithm, COA&#xff09;是一种启发式搜索算法&#xff0c;其设计灵感源自于布谷鸟的独特生活习性&#xff0c;尤其是它们的寄生繁殖行为。该算法通过模拟布谷鸟在自然界中…

Java Spring创建bean流程及自定义构造方法示例

一、spring容器创建bean的简单步骤 1、扫描被Service&#xff0c;Component等注解标识的类。 2、找到类的构造方法 默认使用无参构造方法构建bean&#xff0c;如果类中定义了有参构造方法则会按照有参构造方法构建bean。 3、依赖注入 如果类中存在Autowired或有参的构造方法&am…

如何使用PHP和RabbitMQ实现延迟队列(方式二)?

前言 前几天写了一篇关于PHP和RabbitMQ如何通过插件实现延迟队列的功能。 今天写另外一篇不需要插件的方式&#xff0c;使用RabbitMQ的死信队列&#xff08;Dead-Letter-Exchanges, DLX&#xff09;和消息TTL&#xff08;Time-To-Live&#xff09;。 这种方法涉及到设置消息…

记一次 .NET某防伪验证系统 崩溃分析

一&#xff1a;背景 1. 讲故事 昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障&#xff0c;因为看小伙子昨天在群里问了一天也没搞定&#xff0c;干脆自己亲自上阵吧&#xff0c;抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式&#xff0c;省去了很多沟通…

[flink] flink macm1pro 快速使用从零到一

文章目录 快速使用 快速使用 打开 https://flink.apache.org/downloads/ 下载 flink 因为书籍介绍的是 1.12版本的&#xff0c;为避免不必要的问题&#xff0c;下载相同版本 解压 tar -xzvf flink-1.11.2-bin-scala_2.11.tgz启动 flink ./bin/start-cluster.sh打开 flink web…

RTOS线程切换的过程和原理

0 前言 RTOS中最重要的一个概念就是线程&#xff0c;线程的按需切换能够满足RTOS的实时性要求&#xff0c;同时能将复杂的需求分解成一个个线程执行减轻我们开发负担。 本文从栈的角度出发&#xff0c;详细介绍RTOS线程切换的过程和原理。 注&#xff1a;本文参考的RTOS是RT-T…

<QT基础(5)>事件监听

事件监听 事件监听&#xff08;Event Handling&#xff09;是在程序中监视和响应发生的事件的一种机制。在Qt中&#xff0c;事件监听是一种常见的用于处理用户输入、系统事件以及其他类型事件的方法。通过事件监听&#xff0c;您可以在发生特定事件时捕获事件并执行相应的操作…