宏定义与特别运用

news/2024/9/19 2:27:18/文章来源:https://www.cnblogs.com/DSCL-ing/p/18414574

目录
  • 宏定义
    • 数值宏常量
    • 字符串宏常量
    • 用define宏定义注释符号?
      • 程序的编译过程
      • 预处理中宏替换和去注释谁先谁后?
    • 如何写一个可靠的宏函数
      • do-while-zero结构
      • do-while-zero的评价
    • 宏定义中的空格
    • 宏只能在main函数上面定义吗?
    • 宏的作用范围
    • #undef
      • 宏替换是在函数调用之前进行.
      • 块中进行#define和#undef需要谨慎

宏定义


数值宏常量

#define 宏定义是个演技非常高超的替身演员,但也会经常耍大牌的,所以我们用它要慎之又慎。它可以出现在代码的任何地方,从本行宏定义开始,以后的代码就都认识这个宏了;也可以把任何东西定义成宏。因为编译器会在预编译的时候用真身替换替身,而在我们的代码里面却又用常常用替身来帮忙。看例子:

​ #define PI 3.141592654

在此后的代码中尽可以使用 PI 来代替 3.141592654,而且你最好就这么做。不然的话,如果我要把PI的精度再提高一些,你是否愿意一个一个的去修改这串数呢?你能保证不漏不出错?而使用 PI 的话,我们却只需要修改一次。这种情况还不是最要命的,我们再看一个例子:

​ #define ERROR_POWEROFF -1

如果你在代码里不用 ERROR_POWEROFF 这个宏而将-1硬编码进代码里,尤其在函数返回错误代码的时候(往往一个开发一个系统需要定义很多错误代码)。肯怕上帝都无法知道-1 表示的是什么意思吧。这个-1,我们一般称为“魔鬼数”,上帝遇到它也会发狂的。所以,我奉劝代码里一定不要出现“魔鬼数”。

关键字篇我们讨论了 const 这个关键字,我们知道const 修饰的数据是有类型的,而 define 宏定义的数据没有类型。为了安全,我建议以后在定义一些宏常数的时候用 const 代替,编译器会给 const 修饰的只读变量做类型校验,减少错误的可能。但一定要注意const修饰的不是常量而是readonly的变量,const 修饰的只读变量不能用来作为定义数组的维数,也不能放在 case 关键字后面。


字符串宏常量

#define ENG_PATH_4 "E:\English\listen_to_this\listen_to_this_3"


用define宏定义注释符号?

能否使用宏定义的注释来注释代码?

#define COMMENT //
int main()
{COMMENT puts("hello");
}

第一眼看这个代码可能会搞不清程序是否执行打印,这个问题的解决我们需要知道预处理过程各步骤的执行顺序,

先看看程序预处理过程做了什么.


程序的编译过程

预处理: 预处理指令,头文件展开,去掉注释,宏替换,条件编译 (顺序是怎样的?)

编译: C语言翻译成汇编语言

汇编: 将汇编代码转化成可重定向目标文件(可被链接)

链接: 自身程序+库文件进行关联,形成可执行程序


预处理中宏替换和去注释谁先谁后?

生成的预处理结果如图:

image-20240506124541340

观察结果,如果宏替换先于去注释,则puts代码一定是被去掉的,显然puts还在,说明先去注释,再宏替换;

既然是先去注释再宏替换,那为什么预处理后却没有发现puts前面带双斜杠呢? 这就很尴尬了,其实在#define COMMENT //处的双斜杠在编译前就被识别成注释了,去掉注释后代码就变成了#define COMMET这样子,是一个仅仅用于标识的宏.

预处理指令和宏谁先处理是不可预期的.

总之,通过这点我们知道了预处理过程去注释是先于宏替换的.

上面说的是C++风格的注释,那C风格的注释呢

#define BSC //
#define BMC /*
#define EMC */BSC: Begin Single-line Comment
BMC: Begin Multi-line Comment
EMC: End   Multi-line Comment

image-20240506131827190

这就很明显了,如果有语法提示则很容易看出来,和上面所说的C++风格注释的情况是一样的原理.



如何写一个可靠的宏函数

我们知道,一般的宏函数是很容易出现问题的,比如说少加了括号,因为结合性问题导致代码逻辑没有按照预期来执行...,那怎样写出一个健壮性很高的宏函数呢? 先看一个例子:

image-20240506212559805

如果我定义这样一个宏函数,并且按照一般函数的方式运用,显然不是能通过语法检查的.看一下预处理后的代码

image-20240506213807477

可以发现a = 0;已经算一条语句了,后面b = 0;;多出来,不符合语法,因此报错.


if在不带花括号的条件下只能且必须带一条语句.如果想用这条宏函数,只能将它写进if的花括号中.但是,这样的代码是不友好的,它变相的强迫用户必须带上花括号,显然不是一种很好的方式.

既然要求if分支有多条语句需要执行时必须加上花括号,那能不能直接在宏函数中加上花括号? 看一下效果

image-20240506214720101

再看一下预处理后的代码

image-20240506214859943

可以发现if花括号后面还带上了分号,这显然也不够好.

上面各种方式都是有大大小小的缺陷. 那还有没有更好的方案? 有的,最终解决方案:使用do-while-zero结构


do-while-zero结构

image-20240506222258461

看预处理后的代码:

image-20240506224304956

可以发现,在do-while-zero结构中,do后面有花括号,可以封装任意多条语句.while(0)后可以接上分号,并且while(0)是条件判定为假,结束执行循环,整体上只执行一次且必须执行一次.用法上和普通函数有类似的效果,因此具有普适性.


do-while-zero的评价

do-while-zero结构是一个编码技巧,作为一个宏函数技巧,我们可以了解一下,虽然不一定会使用它.在早些年的项目中也有很多使用的,掌握它后至少我们在看源码时可以在遇到这样子的宏函数时可以知道写的是什么...


宏定义中的空格

#include <stdio.h>#define INC(a) ((a)++) //定义宏函数不能带空格int main()
{   int i = 0;   INC (i); //使用可以带空格,但是严重不推荐   printf("%d\n", i);
}

宏只能在main函数上面定义吗?

先说结论:宏可以在源文件的任何地方定义.

验证,在main函数中定义:

image-20240507152543768

在普通函数中定义:

image-20240507152828624

在普通函数中定义,在main函数中使用:

image-20240507153009444

说明:宏定义与是否在函数体外内没有任何关系

结论:源文件的任何地方,宏都可以定义,与是否在函数内外无关.


宏的作用范围

注意:宏只在从它定义的位置开始生效.从定义开始,往后都是有效的.

不正确例子:

image-20240507153403677


#undef

#undef的作用是取消宏,限定宏的范围.

image-20240507155829310

宏替换是在函数调用之前进行.

看下面一段代码:

image-20240507162514979

#undef在函数调用的上边,这样的代码看着会有些绕.看一下运行结果:

image-20240507162655612

这是可以通过的,因为宏替换是在函数调用之前进行.这样的代码需要熟悉预编译指令的执行顺序才容易阅读.


块中进行#define和#undef需要谨慎

C语言中,尽管在代码文件中的任何位置放置#define或者#undef是合法的,但把它们放在块中会使人误解为它们只存在于块作用域,给人一种只在函数内有效的错觉.

也不排除我们只想让它在局部范围内有效,因此使用时需要慎重考虑.


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

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

相关文章

白云龙期货投资-第六讲

## 价量行情中的配合 1,价涨量增,顺势推动。 2,价量背离,趋势转变 (价与量在相对高位称为顶背离,在下跌末期为底背离。背离后如果想再次上涨或者下跌,一般价格都会出现一个盘整期,而成交量则出现缩量地量期)价量行情中的配合 1,价涨量增,顺势推动。 2,价量背离,趋势…

软件工程结对项目 3:python实现自动生成小学四则运算题目的程序

这个作业属于哪个课程 广工计院计科34班软工这个作业要求在哪里 作业要求团队成员1 庄崇立3122004633团队成员2 罗振烘3122004748这个作业的目标 结对合作完成小学四则运算题目的程序,熟悉项目开发流程,提高团队合作能力一、GitHub地址 二、需求 1.题目:实现一个自动生成小学…

结构、权限修饰符、类

结构、权限修饰符、类 结构(struct:自定义的数据结构) struct student {// 成员变量int number;char name[100];void func(){ // 成员函数(方法)number++;} };// 1. 形参为引用 void func1(student &stu){stu.number = 2000;strcpy_s(stu.name, sizeof(stu.name…

Leetcode 2183. 统计可以被 K 整除的下标对数目

1.题目基本信息 1.1.题目描述 给你一个下标从 0 开始、长度为 n 的整数数组 nums 和一个整数 k ,返回满足下述条件的下标对 (i, j) 的数目:0 <= i < j <= n - 1 且 nums[i] * nums[j] 能被 k 整除。1.2.题目地址 https://leetcode.cn/problems/count-array-pairs-di…

贪心算法-找不重叠的区间段

1.说明 有N个区间片段,查找其中不重叠的片段最大个数。例如(6 8),(2 4),(3 5),(1 5),(5 9),(8 10)这6个片段中,不重叠的片段最大个数为3,分别为(2 4),(6 8),(8 10)。 2.解析 先按照起始位置从小到大进行排序,使用贪心算法使有效片段尽可能小,即结束位置更靠前…

Nuxt Kit 中的页面和路由管理

title: Nuxt Kit 中的页面和路由管理 date: 2024/9/17 updated: 2024/9/17 author: cmdragon excerpt: 摘要:本文介绍了Nuxt Kit中页面和路由管理的高级功能,包括extendPages自定义页面路由、extendRouteRules定义复杂路由逻辑及addRouteMiddleware注册路由中间件。通过这些…

堆的应用

1.需要具备的知识 1.1以顺序存储方式存储完全二叉树 完全二叉树:节点从上到下,从左到右布局的二叉树,如下图所示。完全二叉树可以使用类似数组这种顺序存储的结构存节点,如下图。按照"层级遍历"方式遍历这棵树(还有"前序、中序、后序"遍历方式,这里不做…

Oliver编译安装(Windows10+VisualStudio2022)

Oliver是一个开源的非线性视频编辑器。主要基于Qt和FFmpeg开发。前置条件 电脑上需要的环境Qt(>=5.15) VisualStudio(2022,其他版也可) vcpkg软件安装安装Qt5.15令人糟糕的是,Qt如今变得不太容易安装。自从Qt5.15以后的版本,就取消了离线安装。所有的Qt后序版本就只能通过…

CVE-2021-24762 复现

CVE-2021-24762 复现一看是个wordpress,看了下版本6.0没洞,直接扔wpscan扫一下 发现了个插件一搜发现perfect-survey在1.5.2之前都有洞,直接搜exp打. 上官网找了个API重扫一遍,直接给出了CVE号!直接找个sqlmap的exp跑一下,注意第二个位置选n来设置cookie sqlmap -u "http…

Java后端对前端的数据进行校验

首先,作为一名后端程序员,大家一定要记住:不要相信前端传来的数据,后端程序员仿佛是国家的边境出入局的工作人员,承担这最后的防线,必须尽可能的防止错误信息的流入导致正确信息的流出。因此,后端程序员必须要对前端传来的数据进行校验。这里呢,发现一个很好的校验工具…

vp介绍

想要更多请到:https://budingcat.xyz 注册了解详情 可以点击工单进行沟通哦~

云计算与大数据概论--金功勋

week4的 week5 Hadoop介绍起源:Hadoop as a solution:Building blocks:Namenodeif other nodes fail:DataNode:Block 1Secondary Namenode:JobTracker:if fails:Topolosy clusterPig:Hive :PIGweek10 week13: