不会飞的章鱼

熟能生巧,勤能补拙;念念不忘,必有回响。

预处理命令:让编译器帮你写代码

任务

请你实现一个打印“漂亮日志格式”的方法。

首先我们先说“日志”的作用,程序中的“日志”,通常是指在程序运行过程中,输出的一些与程序当前状态或者数据相关的一些信息。这些信息,可以帮助程序开发人员做调试,帮助运营人员做数据分析,帮助管理人员分析日活等等。总而言之,一份合理的日志信息,是非常有价值的数据。而我们今天呢,接触一种最简单的日志形式,就是程序运行过程中的调试信息。

请你实现一个参数形式和 printf 函数一样的 log 方法,用法如代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

void func(int a) {
log("a = %d\n", a);
}

int main() {
int a = 123;
printf("a = %d\n", a);
log("a = %d\n", a);
func(a);
return 0;
}

你会看到上述代码中,有一个和 printf 名字不一样可用法完全一样的方法叫做 log,而这个 log 的输出结果,和 printf 可不一样。

具体如下:

1
2
3
a = 123
[main, 10] a = 123
[func, 4] a = 123

你会看到 log 的方法,虽然和 printf 函数的用法一致,可在输出内容中,log 方法的输出明显比 printf 函数的输出要多了一些信息。

编码

如何定义一个支持可变参数的log宏:

1
#define log(frm, args...) // 假装这里有内容,后续展开讲解

所以可以将 log 方法的使用方式与 printf 类似了:

1
#define log(frm, args...) printf(frm, args)

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>

#define log(frm, args...) { \
printf("[%s : %d] ",__func__,__LINE__); \
printf(frm, ##args); \
}

//"##"的作用是对token进行连接,这里的args就是token,如果token为空,那么不进行连接,所以允许省略可变参数

int main() {
log("hello world\n");
}

//输出:
//[main : 11] hello world

思考题

1,没有 Bug 的 MAX 宏

请你完善下面代码中的 MAX 宏,MAX 宏的作用,就是接受两个元素,选择出两个元素中的最大值。
完善以后的 MAX 宏,输出需要与如下给出的输出样例一致,注意,只能修改 MAX 宏的定义内容,不可以修改主函数中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#define P(item) printf("%s = %d\n", #item, item);
#define MAX(a, b) // TODO

int main() {
int a = 6;
P(MAX(2, 3));
P(5 + MAX(2, 3));
P(MAX(2, MAX(3, 4)));
P(MAX(2, 3 > 4 ? 3 : 4));
P(MAX(a++, 5));
P(a);
return 0;
}

/*
输出结果参考:

MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
MAX(a++, 5) = 6
a = 7
*/

实现(思考过程是重点)

1
2
3
4
#define MAX(a, b) ({ \
__typeof(a) __a = (a), __b = (b); \
__a > __b ? __a : __b; \
})

小结

  • C 语言的程序编译是一套过程,中间你必须搞懂的有:预处理阶段,编译阶段和链接阶段。
  • 程序最终的功能,是由“待编译源码”决定的,而“待编译源码”是由各种各样的预处理命令决定的。
  • 预处理命令之所以被称为“黑魔法”,是因为编译器会根据预处理命令改变你的源代码,这个过程,神秘而具有力量,功能强大。
  • 代码中反斜杠的后面,不能出现任何其他内容。
  • 宏定义只占用一行代码,为了增强宏定义的代码可读性,我们可以采用在行尾加反斜杠的技巧,来使得上下两行代码,变成编译器眼中的一行代码。
  • 宏的作用,就是替换,要想理解最终的代码行为,必须从宏替换以后的代码入手分析。
  • 条件编译相当于一种预处理阶段的代码剪裁技巧。
  • 编译器预设的宏,有标准的,也有非标准的,非标准的代码会影响其可移植性。
------ 本文结束------
如果本篇文章对你有帮助,可以给作者加个鸡腿~(*^__^*),感谢鼓励与支持!