首页 > 其他分享 >宏定义与特别运用

宏定义与特别运用

时间:2024-09-17 12:51:04浏览次数:5  
标签:特别 定义 代码 注释 运用 define 预处理 函数

目录

宏定义


数值宏常量

#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是合法的,但把它们放在块中会使人误解为它们只存在于块作用域,给人一种只在函数内有效的错觉.

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


标签:特别,定义,代码,注释,运用,define,预处理,函数
From: https://www.cnblogs.com/DSCL-ing/p/18414574

相关文章

  • Python中的魔法:探索自定义Context Manager的魅力
    引言在日常开发中,我们经常需要处理各种资源管理的问题。比如,打开一个文件后需要记得关闭;使用完数据库连接后需要释放等。如果这些操作处理不当,可能会导致内存泄漏或者其他资源浪费的问题。ContextManager的设计正是为了解决这些问题而生,它提供了一种自动化的资源管理方式。那么,如......
  • Python Web开发中的扩展与插件开发:从自定义到打包与发布
    PythonWeb开发中的扩展与插件开发:从自定义到打包与发布目录⚙️Flask中的自定义扩展开发......
  • 自定义类型结构体
    1.结构体类型的声明 structStu{charname[20];//名字intage;//年龄charsex[5];//性别charid[20];//学号};//分号不能丢2.结构体变量的创建和初始化#include<stdio.h>structStu{charname[20];//名字intage;//年龄charsex[5];//性别c......
  • tauri2.x+vue3实践篇|封装多窗口|tauri2.0自定义托盘闪烁消息提示+右键菜单
    最近一直在捣鼓Tauri2.0跨平台框架,之前也有分享几篇tauri1.x实例项目。相较于1.0,tauri2.x框架api有了比较多的变更,而且支持创建android/ios移动端应用。实现类似QQ托盘闪烁消息提醒及右键菜单。框架信息"@tauri-apps/api":">=2.0.0-rc.0","@tauri-apps/cli":">=......
  • 自定义类型:结构体 1
    结构体的声明和初始化1.结构体的声明结构体是用户自定义的数据类型,它能将不同类型的数据组成一个单一的复合类型。定义结构体类型使用struct关键字,后跟结构体名称和大括号内的成员列表。如structstu{ charname[20]; intage;charsex[5];};//分号不能丢2.变......
  • 10、结构体的定义和使用
    定义结构体数组结构体指针结构体嵌套结构体......
  • 树(森林)的定义和画图
     目录代码实现“双亲表示法”顺序存储“孩子表示法”链式存储树的孩子表示法存储v.s.图的邻接表存储v.s.散列表的拉链法v.s.基数排序“孩子兄弟表示法”链式存储画图表示“双亲表示法”1.树2.森林“孩子表示法” 1.树2.森林 “孩子兄弟表示法”1.树2.......
  • urllib自定义opener对象设置代理IP
    urllib.request.urlopen()源代码——urlopen()在干什么返回opener.open(url,data,timeout)方法的结果 _opener=None#_opener被赋值为Nonedefurlopen(url,data=None,timeout=socket._GLOBAL_DEFAULT_TIMEOUT,*,cafile=None,capath=None,cadefault=......
  • 鸿蒙开发入门day18-自定义扩展
    (创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,还请三连支持一波哇ヾ(@^∇^@)ノ)目录自定义扩展能力概述AttributeModifierAttributeUpdaterAttributeModifier概述接口定义行为规格属性设置与修改设置多态样式、事件AttributeUpdater概述接口定......
  • Python编程之旅:定义自定义异常的艺术
    引言在实际开发过程中,我们经常会遇到各种各样的错误情况,如数据类型不符、资源访问失败等。这时候,合理地使用异常处理机制就显得尤为重要了。Python内置了许多异常类,但有时候它们并不能完全满足我们的需求。这时,就需要我们自己动手定义一些特定场景下的异常类型了。定义自定义异常......