高级宏操作编写完毕。

This commit is contained in:
Lion Chan 2018-07-10 22:32:00 +08:00
parent 1c74f156bf
commit c6e2c02697
2 changed files with 85 additions and 22 deletions

View File

@ -275,6 +275,8 @@ int b = REG1_ADDR_1*2; // b=0x25+0x01*2=39
另外,我们很少使用小写英文字母作为宏的名称。
很多参考中将宏理解为编译期间的文本替换,编译器会将宏展开,这与程序执行期间发生的事情有本质差别。
## 2.1.10 typedef
typedef也是一个能够有效提高程序可维护性的关键字。它允许你声明一个自定义的类型。例如

View File

@ -1,11 +1,12 @@
## 2.4 高级宏操作
# 2.4 高级宏操作
“魔术是什么?魔术是错觉。但是错觉是为了给人带来快乐,娱乐和灵感。这是关于信仰、信念、信任。脱离了这些属性,魔术就不再是一种艺术了。”
——《惊天魔盗团》
之前我们把宏理解为简单替换实际上还有很多更神奇的宏操作他们像程序里的魔术师一样让人惊讶神往想一探究竟。但请切记“The closer you look, the less you see。”
之前我们把宏理解为编译时的简单文本替换实际上还有很多更神奇的宏操作他们像程序里的魔术师一样让人惊讶神往想一探究竟。但请切记“The closer you look, the less you see。”
### 2.4.1 条件编译
## 2.4.1 条件编译
宏的第一个魔术,便是保护唯一性,常常在头文件里看到类似下面的内容:
@ -67,30 +68,90 @@ C/C++中的下列宏可以控制编译器的行为:
现在,我们回来解析 some.h 文件:如果没有定义 SOME_H 这个宏,则定义一个 SOME_H 这个宏,并且声明“/* some codes... */”中的内容,如定义了 PI 这个宏。如果谋文件两次以上包含了 some.h 这个文,则从第二次开始,会发现之前已经定义过 SOME_H 宏,因此后续的判断都会失败,从而保证 some.h 中的内容只被包含一次。
*注意some.h 中的保护范围实际上是从 #ifdef 开始到 #endif 结束。在此范围之外的内容仍会被多次包含*
注意some.h 中的保护范围实际上是从 #ifdef 开始到 #endif 结束。在此范围之外的内容仍会被多次包含
条件编译除了被用在头文件中用于避免重复引用外,还被经常被用于增加程序的通用性和可移植性上。如硬件平台有部分差异,造成项目的少部分代码有所不同,此时可以使用条件编译,将有差异的代码放到不同的条件下。对应不同硬件,可以人为改变编译条件,确保编译出对应硬件平台的程序。
### 2.4.2 宏函数
## 2.4.2 宏函数
接下来让我们展示一些高级魔术
接下来让我们要Show一些高级魔术。
```cpp
// TODO: 完善示例增加do while和...可变参数.
#define XN2(x y) ((x##y)*(x##y)) // 这是一个宏函数, 符号 ## 起到字符串链接的作用.
int cr0=2, cr1=3, cr2=4;
#define abc(x y) x##y
abc(cr, 0)
abc(cr, 1)
printf("%d", XN2(cr, 0)); // print 4.
printf("%d", XN2(cr, 1)); // print 9.
printf("%d", XN2(cr, 2)); // print 16.
```
### 2.4.3 编译器内置宏
你看到的没错,宏是可以接收参数的,在编译时宏的形参被替换为实参。上述宏中使用了 ## 作为字符串链接符,因此第一个 printf 处被替换为:((cr0)*(cr0))。
宏这个魔术师有一些内置技能
__FILE__
__LINE__
__FUNCTION__
__DATE__
__TIME__
这里有个长一点儿的宏函数:
### 练习
```cpp
#define PI 3.1415
#define Volume(v, s, r, h) \
do { \
(s)=PI*(r)*(r); \
(v)=(s)*(h) \
}while(0)
```
通过 C/C++ 的换行符,我们将一个比较长的宏函数写成了多行。这段程序用于计算圆柱体体积。问题是上面的 do...while(0) 是用来做什么的?
上面这种写法可以避免在宏替换时出现 if...else 不匹配的情况:
```cpp
// 下面 a, v, s 为预先定义好的变量,注意 v 和 s 的值会发生变化.
if(a)
Volume(v, s, 5, 10);
else
Volume(v, s, 3, 15);
```
可以想象,上面的宏 Volume 如果没有被 do...while(0) 包围,则 if 后面有多条语句且没有形成语句块,这就造成 if 没有与后面的 else 配对,出现语法错误。
注意:与普通函数不同,宏函数并不对参数进行类型检查。并且宏参数是基于替换原则,而非函数参数的值传递,这意味着宏中参数的变化直接影响实际参数的值。
## 2.4.3 编译器内置宏
宏这个魔术师自带一些天然属性,这就是编译器内置宏,这些宏不需要在程序中定义,而可以直接使用:
| 宏名 | 描述 |
|--------------|-----------------------------------|
| __FILE__ | 编译时被替换问当前源码文件名 |
| __LINE__ | 编译时被替换问当前所在行的行号 |
| __FUNCTION__ | 编译时被替换为当前所在函数的函数名 |
| __DATE__ | 编译时被替换为编译的日期 |
| __TIME__ | 编译时被替换为编译的时间 |
| __VA_ARGS__ | 可变宏参数,与宏参数中的 ... 配对使用 |
以上内置宏常被用于程序的调试工作:
```cpp
#define DEBUG_LEVEL 1
#define DEBUG(level, fmt, ...) \
do { \
if(level<DEBUG_LEVEL) \
printf(fmt, __VA_ARGS__); \
}while(0)
DEBUG(0, "file:%s, func:%s, line:%d\n", __FILE__, __FUNCTION__, __LINE__); // 打印当前文件名, 函数名和行号.
DEBUG(0, "compile date:%s, time:%s.\n", __DATE__, __TIME__); // 打印编译日期和时间.
DEBUG(1, "Hello.\n"); // 在运行时, 此行不会被打印.
```
## 练习
已知双向链表表头结构如下:
```cpp
struct list_head {
struct list_head *next, *prev;
};
```
使用宏创建并初始化一个双向链表。