Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand the Macros in CumberCraft +

2025-04-07 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

这篇文章主要讲解了"怎么理解C/C++中的宏",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么理解C/C++中的宏"吧!

宏的定义

通过 #define 指令定义一个宏。

#define NAME_OF_MACRO value

比如,以下代码定义了一个名为 BUFFER_SIZE 的宏,指代 1024 这个数字。

#define BUFFER_SIZE 1024

使用时,

foo = (char *) malloc (BUFFER_SIZE);

使用预处理器编译:

$ gcc -E test.c

编译结果:

foo = (char *) malloc (1024);

多行

宏的定义是跟随 #define 在一同一行内的,但可通过 反斜杠 \ 实现换行从而定义出多行的宏。

多行的宏经过编译后会还原到一行中。

test.c

#include #define GREETING_STR \ "hello \world"int main() { printf(GREETING_STR); }

编译后:

int main() { printf("hello world"); return 0;}

宏展开时的顺序

宏的展开是在处理源码时按照其出现位置进行的,如果宏定义有嵌套关系,也是层层进行展开,比如:

#include #define GREETING_NAME "wayou"#define GREETING "hello," GREETING_NAMEint main() { printf(GREETING); return 0;}

首先遇到 GREETING,将其展开成 GREETING_NAME "wayou",然后发现另一个宏 GREETING_NAME,将其展开最后得到 "hello," "wayou"。所以编译后的代码为:

int main() { printf("hello," "wayou"); return 0;}

其展开的顺序并不是宏定义时的顺序,为了验证,可将上面示例代码中两个宏的定义调换一下,得到:

-#define GREETING_NAME "wayou"#define GREETING "hello," GREETING_NAME+#define GREETING_NAME "wayou"

再次编译查看产出,会发现没有区别,也不会报 GREETING 中所依赖的 GREETING_NAME 找不到的错。其实 #define 只是告诉编译器定义了这么个宏,而具体的求值,则是使用宏的地方才开始的。

像下面这样,当宏存在覆盖时,会以新的为准,其结果为 37。

#define BUFSIZE 1020#define TABLESIZE BUFSIZE#undef BUFSIZE#define BUFSIZE 37

Object-like 宏

Object-like 类型的宏看起来就像普通的数据对象,故名。多用于数字常量的情形下。且宏名一般使用全大写形式方便识别。像上面示例中,都是 Object-like 的。

Function-like 宏

也可定义出使用时像是方法调用一样的宏,这便是 Function-like 类型的宏。

#define lang_init() c_init()lang_init()// 编译后c_init()

函数类型的宏只在以方法调用形式使用时才会被展开,即名称后加括号,否则会被忽略。当宏名和函数名重名时,这一策略就会显得有用了,比如:

extern void foo(void);#define foo() /* optimized inline version */… foo(); funcptr = foo;

这里 foo() 的调用会来自宏里面定义的那个函数,而 funcptr 会正确地指向函数地址,如果后者也被宏展开,则成了 funptr=foo() 显然就不对了。

函数类型的宏在定义时需注意,宏名与后面括号不能有空格,否则就是普通的 Object-like 类型对象。

#define lang_init () c_init()lang_init()// 编译后:() c_init()()

宏的参数

函数类型的宏,可以像正常函数一样指定入参,入参需为逗号分隔合法的 C 字面量。

#define min(X, Y) ((X) < (Y) ? (X) : (Y)) x = min(a, b); → x = ((a) < (b) ? (a) : (b)); y = min(1, 2); → y = ((1) < (2) ? (1) : (2)); z = min(a + 28, *p); → z = ((a + 28) < (*p) ? (a + 28) : (*p));

入参中的括号

入参中只需要括号对称,但不要求方括号或花括号成对出现,所以下面的代码:

macro (array[x = y, x + 1])

其入参实际为 array[x = y 和 x + 1]。

入参的展开

入参本质上也是宏,对象类型的宏,在函数宏展示时,这些参数也被展示到了函数宏的函数体里。

min (min (a, b), c)

首先被展开成:

min (((a) < (b) ? (a) : (b)), (c))

然后进一步展开成(此处换行为方便阅读,实际编译后没有):

((((a) < (b) ? (a) : (b))) < (c) ? (((a) < (b) ? (a) : (b))) : (c))

参数的缺省

函数宏在使用时其入参可缺省,但不能全部缺省,至少提供一个入参。

min(, b) → (( ) < (b) ? ( ) : (b))min(a, ) → ((a ) < ( ) ? (a ) : ( ))min(,) → (( ) < ( ) ? ( ) : ( ))min((,),) → (((,)) < ( ) ? ((,)) : ( ))min() error→ macro "min" requires 2 arguments, but only 1 givenmin(,,) error→ macro "min" passed 3 arguments, but takes just 2

字符化/Stringizing

如果函数宏中入参在字符串中,是不会被展开的,它就是普通的字符串字面量,这样的结果是符合预期的。

#define foo(x) x, "x"foo(bar) → bar, "x"

但如果确实想将入参展开成字符串,可在使用入参时,加上 # 前缀。

#define WARN_IF(EXP) \do { if (EXP) \ fprintf (stderr, "Warning: " #EXP "\n"); } \while (0)WARN_IF (x == 0); → do { if (x == 0) fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0);

此处 #EXP 在字符串中会被正确展开。What's more, 如果这里的 x 也是宏,那只会在 if 语句中进行展开。

拼接

通过 ## 可将两个宏展开成一个,即将两者进行了拼接,这种操作叫 "token pasting",或 "token concatenation",就是拼接嘛。

宏拼接一般用在需要拼接的宏是来自宏参数的情况,其他情况,大可直接将两个宏写在一起即可,用不着 ## 指令。

考察下面这个场景,其中命令名重复出现:

struct command{ char *name; void (*function) (void);};struct command commands[] ={ { "quit", quit_command }, { "help", help_command }, …};

通过定义宏配合拼接,可达到精简代码的目的:

#define COMMAND(NAME) { #NAME, NAME ## _command }struct command commands[] ={ COMMAND (quit), COMMAND (help), …};

不定参数

像普通函数一样,函数类型的宏也可定义接收不定参数。

#define eprintf(…) fprintf (stderr, __VA_ARGS__)

调用时,命名参数后面,包括逗号都会进入到 __VA_ARGS__ 关键字当中。但 C++ 中还支持对这些参数命名从而不用 __VA_ARGS__。

eprintf ("%s:%d: ", input_file, lineno)// 编译后:fprintf (stderr, "%s:%d: ", input_file, lineno)

C++ 中可这么写:

#define eprintf(args…) fprintf (stderr, args)

不定参数与命名参数混合的情况

不定参数为命名参数后面省略的部分。

#define eprintf(format, …) fprintf (stderr, format, __VA_ARGS__)

预设的宏

标准库及编译器中预设了一些有用的宏,可以在这里 查阅。

取消和重置宏

当某个宏不再使用时,可通过 #undef 将取注销掉。#undef 后紧跟宏名,后面不要跟其他东西,即使是函数类型的宏。

#define FOO 4x = FOO; → x = 4;#undef FOOx = FOO; → x = FOO;

两个宏相似的定义

满足以下条件时,我们认为两者是相似的:

类型相同,比如同为对象类型,或函数类型的宏 展开后各位置的符号(token)相同 如果是函数宏,入参相同 空白的不限但出现的位置相同

比如,下面这些是相似的:

#define FOUR (2 + 2)#define FOUR (2 + 2)#define FOUR (2 /* two */ + 2)

而下面这些则不然:

#define FOUR (2 + 2)#define FOUR ( 2+2 ) // 空白位置不一样 #define FOUR (2 * 2) // 宏的内容不一样#define FOUR(score,and,seven,years,ago) (2 + 2) // 入参不一样

宏重复定义时的表现

对于使用了 #undef 注销过的宏,再次定义同名的宏时,要求新定义的宏不与老的相似。

而如果说一个已经存在的宏,并没有注销,重复定义时,如果相似,则新的定义会忽略,如果不相似,编译器会报警告同时使用新定义的宏。这允许在多个文件中定义同一个宏。

感谢各位的阅读,以上就是"怎么理解C/C++中的宏"的内容了,经过本文的学习后,相信大家对怎么理解C/C++中的宏这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report