学习思考
🏝️绿导师一段代码逼我学会宏定义😥
00 分钟
2022-12-15
2023-8-13
type
status
date
slug
summary
tags
category
icon
password
Property
Aug 13, 2023 03:50 AM
起因是这样,对于我这么一个c语言基础不太好的同学来说,在看绿导师第二节课的时候直接蒙了:
notion image
你确定这是宏??(好吧全是我的问题)
下边根据网上的资料对宏的内容进行一些总结:

C语言中用到宏定义的地方很多,如在头文件中为了防止头文件被重复包含,则用到:

在我们常用的 stdio.h 头文件中也可以见到很多宏定义,如:
从开始写C语言到生成执行程序的流程大致如下(姑且忽略预处理之前的编译器的翻译处理流程等),在进行编译的第一次扫描(词法扫描和语法分析)之前,会有由预处理程序负责完成的预处理工作。
notion image
预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令 #include,宏命令 #define 等,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计。
本文主要介绍宏定义的以下几个部分:
notion image

1、概念及无参宏

一种最简单的宏的形式如下:
无参宏是指宏名之后不带参数,上面最简单的宏就是无参宏。
注意宏不是语句,结尾不需要加“;”,否则会被替换进程序中,如:
以上几个宏都是用来代表值,所以被成为类对象宏(object-like macro,还有类函数宏,下面会介绍)。
如果要写宏不止一行,则在结尾加反斜线符号使得多行能连接上,如:
注意第二行要对齐,否则,如:
也就是行与行之间的空格也会被作为替换文本的一部分
而且由这个例子也可以看出:宏名如果出现在源程序中的“”内,则不会被当做宏来进行宏代换。

宏可以嵌套,但不参与运算:

宏代换的过程在上句已经结束,实际的 5 * 5 相乘过程则在编译阶段完成,而不是在预处理器工作阶段完成,所以宏不进行运算,它只是按照指令进行文字的替换操作。再强调下,宏进行简单的文本替换,无论替换文本中是常数、表达式或者字符串等,预处理程序都不做任何检查,如果出现错误,只能是被宏代换之后的程序在编译阶段发现。
宏定义必须写在函数之外,其作用域是 #define 开始,到源程序结束。如果要提前结束它的作用域则用 #undef 命令,如:
也可以用宏定义表示数据类型,可以使代码简便:
如果重复定义宏,则不同的编译器采用不同的重定义策略。有的编译器认为这是错误的,有的则只是提示警告。Xcode中采用第二种方式。如:
这些简单的宏主要被用来定义那些显式常量(Manifest Constants),而且会使得程序更加容易修改,特别是某一常量的值在程序中多次被用到的时候,只需要改动一个宏定义,则程序中所有出现该变量的值都可以被改变。而且宏定义还有更多其他优点,如使得程序更容易理解,可以控制条件编译等。
#define 与 #typedef 的区别:
两者都可以用来表示数据类型,如:
两者是等效的,调用也一样:
但当如下使用时,问题就来了:
因为 INT1 a1, b1; 被宏代换后为: int * a1, b1;即定义的是一个指向int型变量的指针 a1 和一个int型的变量b1.而INT2 a2, b2;表示定义的是两个变量a2和b2,这两个变量的类型都是INT2的,也就是int *的,所以两个都是指向int型变量的指针。
所以两者区别在于,宏定义只是简单的字符串代换,在预处理阶段完成。而typede不是简单的字符串代换,而是可以用来做类型说明符的重命名的,类型的别名可以具有类型定义说明的功能,在编译阶段完成的。

2、有参宏

C语言中宏是可以有参数的,这样的宏就成了外形与函数相似的类函数宏(function-like macro),如:
notion image
宏调用:
宏名(实参表);
和函数类似,在宏定义中的参数成为形式参数,在宏调用中的参数成为实际参数。
而且和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参:
这看上去用法与函数调用类似,但实际上是有很大差别的。如:
这两个结果和调用函数的方法的结果差别很大,因为如果是像函数那样的话,COUNT(x + 1)应该相当于COUNT(7),结果应该是 7 * 7 = 49,但输出结果却是21。原因在于,预处理器不进行技术,只是进行字符串替换,而且也不会自动加上括号(),所以COUNT(x + 1)被替换为 COUNT(x + 1 * x + 1),代入 x = 6,即为 6 + 1 * 6 + 1 = 13。而解决办法则是:尽量用括号把整个替换文本及其中的每个参数括起来:
但即使用括号,也不能解决上面例子的最后一个情况,COUNT(++x) 被替换为 ++x * ++x,即为 7 * 8 = 56,而不是想要 7 * 7 = 49,解决办法最简单的是:不要在有参宏用使用到“++”、“–”等。
上面说到宏名中不能有空格,宏名与形参表之间也不能有空格,而形参表中形参之间可以出现空格:
如果用函数求一个整数的平方,则是:
所以在宏定义中:#define COUNT(M) M * M 中的形参不分配内存单元,所以不作类型定义。而函数 int count(int x)中形参是局部变量,会在栈区分配内存单元,所以要作类型定义,而且实参与形参之间是“值传递”。而宏只是符号代换,不存在值传递。

宏定义也可以用来定义表达式或者多个语句。如:

3. # 运算符

比如如果我们宏定义了:
我们想要输出“1 + 2 + 3 + 4 = 10”,用以下方式显得比较麻烦,有重复代码,而且中间还有括号:
那么这时可以考虑用 # 运算符来在字符串中包含宏参数,# 运算符的用处就是把语言符号转化为字符串。例如,如果 a 是一个宏的形参,则替换文本中的 #a 则被系统转化为 “a”。而这个转化的过程成为 “字符串化(stringizing)”。用这个方法实现上面的要求:
调用宏时,用 1 + 2 代替 a,用 3 + 4 代替b,则替换文本为:printf(“1 + 2” ” + ” “3 + 4” ” = %d\n”,((1 + 2) + (3 + 4))),接着字符串连接功能将四个相邻的字符串转换为一个字符串:"1 + 2 + 3 + 4 = %d\n"

4、## 运算符

和 # 运算符一样,## 运算符也可以用在替换文本中,而它的作用是起到粘合的作用,即将两个语言符号组合成一个语言符号,所以又称为“预处理器的粘合剂(Preprocessor Glue)”。用法:
NAME(0)被替换为 num ## 0,被粘合为: num0。

5、可变宏:… 和 __VA_ARGS__

我们经常要输出结果时要多次使用 prinf(“…”, …); 如果用上面例子#define SUM(a,b) printf(#a ” + “#b” = %d\n”,((a) + (b))),则格式比较固定,不能用于输出其他格式。
这时我们可以考虑用可变宏(Variadic Macros)。用法是:
在宏定义中,形参列表的最后一个参数为省略号“…”,而“__VA_ARGS__”就可以被用在替换文本中,来表示省略号“…”代表了什么。而上面例子宏代换之后为: printf(“hello\n”);
还有个例子如:
在宏调用中,X的值为10,所以 #X 被替换为”1”。宏代换后为:
接着这4个字符串连接成一个:
要注意的是:省略号“…”只能用来替换宏的形参列表中最后一个!
 

参考


评论
Loading...