11 | 预处理命令(下):必须掌握的“黑魔法”,让编译器帮你写代码

你好,我是胡光,欢迎回来。最近为了防范疫情,很多人应该都窝在家里吧?春节假期除了娱乐放松,也不要忘记学习提高呀!

上次呢,我们知道了,原来程序的编译,是一个复杂的过程,其中重要的是三个阶段:预处理阶段编译阶段链接阶段

同时,我们也搞清楚了“源代码”和“待编译源码”两个概念的区别,其中“待编译源码”是由“源代码”经过预处理阶段所产生的代码,并且“待编译源码”才是决定程序最终功能的终版代码。

今天呢,我们继续上节课的知识,来具体学习几个重要的,能够影响“待编译源码”内容的预处理命令吧。

本次任务

在正式开始今天课程之前,我们先来回顾一下任务内容:实现一个使用方法和 printf 函数一样的,但是输出信息却比 printf 更加人性化的,更加具体的 log 方法。

具体代码及事例,参考如下:

#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;
}
a = 123
[main, 10] a = 123
[func, 4] a = 123

通过文稿代码可以看到,经过log方法后,我们获得了更多程序信息。但我们的任务是设计完 log 方法以后,请再给这个 log 方法提供一个小开关,使其很方便的打开或者关闭程序中所有 log 的输出信息。

回顾完了任务以后,就让我们一起来进行具体的预处理命令的学习吧。

必知必会,查缺补漏

在上一节,我们明确了 include 文件包含预处理命令的作用。今天,我们将来着重讲解两种预处理命令宏定义条件编译。它们是什么意思呢?不要着急,听我一个个给你解释。

1. 初识宏定义

宏定义在预处理阶段的作用,就是做简单的替换,将 A 内容,替换成 B 内容,这里需要你特别注意的是,一个宏定义只能占一行代码,这可不是你所认为的一行代码,而是编译器所认为的一行代码,这里在后面,我们会详细来介绍一下。

这里先给你准备了一张示意图,用来说明宏定义的基本用法:

正如上图所示,宏定义以 #define 作为语句的开头,之后两部分,用空格分隔,在预处理阶段期间,会把代码中的 A 内容替换成 B 内容,以此来最终生成“待编译源码”。

下面我们就使用宏来实现一个读入圆的半径,输出圆面积的程序:

#include <stdio.h>
#define PI 3.1415926

int main() {
    double r;
    scanf("%lf", &r);
    printf("%lf\n", PI * r * r);
    return 0;
}

在上面程序中,我们定义了一个名字为 PI 的宏,其替换内容为3.1415926,也就是圆周率π的相似值。在主函数中,我们读入一个圆的半径值,存储在 r 变量中,然后输出圆的面积,在计算圆面积公式的时候,我们没有使用圆周率本来值来进行程序书写,而是使用刚刚上面定义的宏 PI 代替了圆周率的作用。

面对这份源代码,在预处理阶段的时候,编译器会把代码中所有使用 PI 的地方,都替换成3.1415926,也就是说,上述代码中的输出函数中,原本的 PI * r * r 的代码内容,会被编译器改写成为 3.1415926 * r * r 作为“待编译源码”。

通过这个例子,我想你就能差不多明白了,什么叫做“宏定义在预处理阶段做的就是简单的替换”以及“宏定义在代码中,只能占一行”,简单来说,就是宏定义关键字原内容和替换内容 三者必须写到一行

2.宏定义之傻瓜表达式

前面呢,我们说的是宏定义的最基本用法。其实,宏定义中的“原内容”的形式,不仅仅有刚才的类似于 PI 这种简单符号,还有一种更加灵活实用的带参数的形式,如图所示:

可以看到,我们定义了一个支持两个参数的宏,名字为 mul,替换的内容为 a * b。注意,替换内容中的 a 是宏参数中的 a,b 也是宏参数中的 b。这里我再强调一下,理解宏的工作过程,始终离不开那句话:宏做的就是简单替换

下面给你举个例子:

#include <stdio.h>
#define mul(a, b) a * b

int main() {
    printf("mul(3, 5) = %d\n", mul(3, 5));
    printf("mul(3 + 4, 5) = %d\n", mul(3 + 4, 5));
    return 0;
}

上面代码中,使用了 mul 宏,分别输出了 mul(3, 5) 的值,和 mul(3 + 4, 5) 的值。如果你把 mul 当成函数看待的话,你应该会觉得,第一行输出的值应该是 15,即 3 * 5 结果;第二行应该是 35,计算的应该是 7 * 5 的结果。

可如果你在你的环境中运行这个代码,你会看到第一行输出的结果确实是 15,和我们的预期一样,可第二行输出的却是 23,这个离我们预想的可就有点儿不一样了。

想要理解为什么输出的是 23,而不是 35 的话,我们需要综合以下两点来进行思考:

  • “待编译源码”决定了最终程序的功能。
  • 宏做的就是简单的替换。

宏在预处理阶段将被展开,变成“待编译源码”中的内容,并且做的仅仅是简单的替换。也就是说,mul(a, b) 这个宏,替换的形式是 a * b;而 mul(3 + 4, 5) 中 3 + 4 是参数 a 的内容,5 是 b 的内容,依次替换为 a*b 式中的 a,b 的话,最终得到的替换内容应该是 “3 + 4 * 5”,这个才是“待编译源码”中真正的内容。面对这个替换以后的表达式,你就知道为什么输出的结果是 23,而不是 35 了吧。

所以,正如你所看到的,mul 的使用形式虽然和函数类似,可实际运行原理和函数完全不一样,甚至显得有些机械化。因为 mul 是宏,而宏做的就是简单的替换操作,变成最终的“待编译源码”中的内容。这个过程机械且简单,所以,我们有时也称其为傻瓜表达式

再回来看上面的 mul 宏,使用形式像函数,但函数可以在代码中写成多行的一段代码。可宏呢,只能写成一行,就会使得当我们面对稍微复杂一点的替换内容,宏代码的可读性就会变得特别差。

还好,C 语言给我们提供了一种在行尾加 \(反斜杠)的语法,以此来告诉编译器,本行和下一行其实是同一行内容。这样就做到了:人在阅读代码的时候,看到的是两行代码,而编译器在解析的时候,会认为是一行代码,也就解决了复杂的宏定义的可读性的问题。

具体事例,看如下代码:

#include <stdio.h>
#define swap(a, b) { \
    __typeof(a) __temp = a; \
    a = b, b = __temp; \
}

int main() {
    int num_a = 123, num_b = 456;
    swap(num_a, num_b);
    printf("num_a = %d\n", num_a);
    printf("num_b = %d\n", num_b);
    return 0;
}

如上代码中,我们定义了一个用于交换两个变量值的宏 swap,代码的第 2、3、4 行的末尾都有一个反斜杠,编译器就会认为把程序的这几行内容当成一行内容来对待。这样,既保证了宏定义的只占用一行的语法要求,又兼顾了代码可读性。

需要特别注意的是,代码中反斜杠的后面,不能出现任何其他内容。作为新手的话,这里是最容易出错的,很多人会在反斜杠后面多打一个空格,会导致反斜杠失去原本的作用,代码查错的时候,也不容易被发现,这里一定要十分小心。

此外,你看到上述代码中,多了一个__typeof方法,关于这个方法的作用呢,给你留个小的作业题,请你自行查阅相关资料,并用一句话描述 __typeof 的作用。欢迎在专栏的留言区里面写下你认为足够简洁的 __typeof的功能描述。

3. 初识条件编译

看完了宏定义之后,下面来让我们看看另一个使用的比较频繁的预处理命令:条件编译。说到条件编译,光看名字,你也许会联想到 if 条件分支语句。对,条件编译,就是预处理阶段的条件分支语句,其主要作用是根据条件,决定“源代码”中的哪些代码,接下来会被预处理继续进行处理。

我们先来从最容易理解的条件编译开始看起,来了解一下条件编译的语法格式:

如图所示,这个条件编译以指令 #ifdef 作为开头,后面接了一个 Debug。意思是如果定义了Debug 这个宏,就让预处理器继续处理“代码内容1”,否则就处理“代码内容2”。记住,条件编译,可以没有 #else 部分,可最后一定要以 #endif 作为结束。

下面给你举个简单的例子:

#include <stdio.h>
#define Debug
#ifdef Debug
#define MAX_N 1000
#else
#define MAX_N 5000
#endif

int main() {
    printf("MAX_N = %d\n", MAX_N);
    return 0;
}

如果你运行上面这段代码,你的程序一定会输出 MAX_N = 1000,那是因为当代码运行到条件编译的时候,由于之前定义了 Debug 宏,条件编译的条件成立,保留的是第 4 行代码内容,所以主函数中的 MAX_N 宏最终就会被替换成为 1000。

如果你将第2行代码去掉的话,那么条件编译的条件就不成立了,最终被保留下来的是第 6 行代码,程序就会输出 MAX_N = 5000,关于这点,你可以自行尝试一下。

其实在条件编译中,除了我们刚才讲到的三个指令:#ifdef、#else、#endif 之外,还有 #if、#ifndef 以及 #elif 等指令。关于剩下的三个指令的含义和作用,有了这个基础之后,你就可以很轻松的学会了,我就不再赘述了。

我在这里给你准备了一张对照表,以说明这 6 个指令各自的作用:

一起动手,搞事情

思考题:没有 Bug 的 MAX 宏

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

#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

输出漂亮的日志格式

准备完了上面的这些基础知识以后,下面来让我们回到最开始的那个任务。

首先我们来思考,要实现一个和 printf 使用方式一样的 log 方法, printf 函数是一个变参函数,那么 log 也需要支持变参,而 log 方法又比 printf 输出的更人性化一些,其中包括了可以输出所在的函数信息,以及所在的代码位置信息。这里,我们选择使用宏定义来实现所谓的 log 方法。

下面,就给你再补充一个小知识点,就是如何定义一个支持可变参数的 log 宏,看如下代码:

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

如上代码所示,在最后一个参数后面,加上三个点,就代表,这个宏除了第一个 frm 参数以外,后面接收的参数个数是可变的,那么后面的参数内容,统一存放在参数 args 中。

这样,我们就可以设计如下代码,使得 log 方法的使用方式与 printf 类似了:

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

此时,log 方法的输出内容,只是和 printf 方法的输出内容是一致的,还无法输出所在函数以及所在代码位置的相关信息。

下面,我们来补充最后一个知识点,就是编译器会预设一些宏,这些宏会为我们提供很多与代码相关的有用信息,具体如下表所示:

我们看到表中有两个宏,是我们这个任务所需要的,一个是 __func__代表了当前所在的函数名,另一个是__LINE__代表了当前行号。

其中宏__func__后面的说明中,注明了是“非标准”,什么叫做非标准呢,也就是说,在不同的编译器中,这个宏的名称可能是不同的,甚至某些编译器不提供这个宏,也是有可能的。例如在 VC 6.0 的环境中就没有__func__宏,因为这个宏不是 C 语言标准里面的东西。

通过这个__func__宏,我想让你初步认识到什么是代码的 “可移植性”,也就是说,你写了一份代码,当你的运行环境发生改变时,你的代码到底要不要做修改?如果要做修改,到底要做多少修改?这是代码的可移植性所讨论的问题。

放到今天这个例子中,就是说,如果你在你的代码中,不做任何处理的,直接使用__func__宏,那么就会影响你代码的可移植性。如果还不清楚什么是代码的可移植性,你就回想一下,当初我们输出彩色文字的那个代码,是不是在有些人的环境中,无法输出彩色文字?

最后,有了这些基础知识以后,就不难完成这个任务了,下面是我给出的 log 宏的参考代码:

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

正如你看到的,log 宏的定义中,使用了编写多行宏的技巧,就是在行尾添加反斜杠,以达到增强代码可读性的目的。然后 log 宏中,包含两个 printf 输出语句,第一个 printf 语句,输出函数以及代码位置信息;第二个 printf语句,输出 log 宏所接收的内容。

至此,我们看似完成了最初的任务,可不要高兴太早,所有与宏相关的东西,都没那么简单。上面的这个实现,其实是有 Bug,不信的话,你就在你的环境中,尝试像如下代码一样调用 log 宏:

log("hello world\n");

这个就是今天给你留的最后一个需要自己独立解决的小 Bug,记住,勤用及善用搜索引擎,会大大提升你的学习效率和效果。至于如何方便的开关日志输出,参考今天的条件编译,思考一下,我相信这个难不倒你。

课程小结

通过这个任务呢,我们大体的认识了预处理命令家族,算是全方位地了解了宏及条件编译相关的内容。下面呢,我来给你做一下今天这节课的课程小结:

  1. 宏定义只占用一行代码,为了增强宏定义的代码可读性,我们可以采用在行尾加反斜杠的技巧,来使得上下两行代码,变成编译器眼中的一行代码。
  2. 宏的作用,就是替换,要想理解最终的代码行为,必须从宏替换以后的代码入手分析。
  3. 条件编译相当于一种预处理阶段的代码剪裁技巧。
  4. 编译器预设的宏,有标准的,也有非标准的,非标准的代码会影响其可移植性。

至此,我们就完成了“语言基础篇”的全部内容,从下一节开始呢,我们将进入注重培养编程思维“编码能力训练篇”的学习。届时,我们的学习更偏重于思维方式的训练和讲解,不会像语言基础篇一样,有这么多零零碎碎的知识点。我也相信,只要你勤于思考,就一定跟得上学习节奏。

好了,今天就到这里了,我是胡光,我们“编码能力训练篇”,不见不散。

精选留言

  • Aureliano

    2020-02-27 22:02:34

    最开始用#define MAX(a,b) ((a)>(b)?(a):(b)),到a++的时候就得不到预期答案,因为宏定义只是单纯替换语句,待编译文件中就在比较(a)>(b)时调用了一次a++,后面(a):(b)时也调用了一次,于是想到把表达式的值作为变量存储起来,这样就不存在二次调用了,然而还是出现了一系列错误,最后查资料发现如果定义程序段要返回值的话需要在{}外用()扩起,调试之后输出与预期输出一致
    #include<stdio.h>
    #define P(item) printf("%s = %d\n",#item,item)
    #define MAX(a,b) ({ \
    __typeof(a) _a =a; \
    __typeof(b) _b =b; \
    _a>_b?_a:_b; \
    })
    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;

    }
    作者回复

    d(^_^o)非常棒!

    2020-02-28 12:52:10

  • Linuxer

    2020-03-01 19:55:53


    #define log(frm, args...) { \ // 原来这个地方就缺少了反斜线吧
    printf("[%s : %d] ",__func__,__LINE__); \
    printf(frm, ##args); \
    }
    "##"的作用是对token进行连接,这里的args就是token,如果token为空,那么不进行连接,所以允许省略可变参数
    作者回复

    完美!

    2020-03-02 09:58:04

  • 吕作晶

    2020-03-29 23:54:01

    _typeof()作用:声明和已知类型一样类型的新变量
    作者回复

    yes!

    2020-03-30 11:26:25

  • 罗耀龙@坐忘

    2020-06-01 03:24:00

    茶艺师学编程

    我在这,卡了好久……

    1、没想到括号的作用是这么的大……
    /*宏定义之傻瓜表达式 修改版*/
    #include <stdio.h>
    #define mul(a, b) (a)* b
    int main(){
    printf("mul(3 ,5) = %d\n", mul(3, 5));
    printf("mul(3 + 4, 5) = %d\n", mul(3 + 4, 5));
    return 0;
    }

    2、我补上了“空格”
    /*课文例子修改bug*/
    #include <stdio.h>
    /*下面的define就是在定义log(强化版printf)*/
    #define log(frm, args...){\
    printf ("所在位置[%s : %d] ",__func__,__LINE__);\
    printf (frm, args);\
    }
    /*定义func函数(其中引用上面的log)*/
    void func(char *a){
    log("a = %s\n", a);
    }
    int main(){
    printf("请输入要处理的内容:\n");
    char a[200] ;
    scanf("%[^\n]s", a); //这里的%[^\n]s是关键,不然遇到空格就····
    printf("a = %s\n", a);
    log("a = %s\n", a);
    func(a);
    return 0;
    }

    3、一开始,我没有读懂题,就在捣腾printf的宏。
    然后知道目标是MAX,也是花了大把的时间。
    到最后,成品也有一个是不符合输出要求的……
    我就是想不明白为什么a就是会加两次1得8呢?
    /*作业半成品*/
    #include <stdio.h>
    #define P(item) printf("%s = %d\n", (#item), (item))
    #define MAX(a, b) (a > (b) ? (a - 1) : b )
    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;
    }
    作者回复

    非常棒!!可以加我微信交流:huguang_AOA

    2020-06-15 17:31:07

  • 1043

    2020-04-08 23:03:29

    \ 后面加了空格有办法一眼就看出来吗?换编程终端后面的黑白版颜色能看出来吗?宏所替换的计算功能也是先算乘除法后算加减法有括号先算括号吗?编译器预设的宏有标准和非标准的区别,是宏的状态是标准/非标准化,还是编译器是分标准/非标准化?比如同样用c语言编写,其中一份给Linux的gcc编译就是Linux的程序,用华为的方舟编译器编译完成后就是鸿蒙的程序了吗?
    作者回复

    1、基本看不出来,有些文本编辑器会把空格显示成一个灰色的暗点,这样的话,是可以看出来的。
    2、宏替换以后的内容,就相当于正常的程序代码,正常的程序代码怎么运行,就怎么运行。
    3、宏分成标准和非标准的,所谓标准,就是普通话,而每个编译环境多多少少都有自己的口音。
    4、你可以这样理解,不过其中的细节还挺多,其实你也可以用gcc编译windows的exe可执行程序,编译是一整套的流程,某些环节依赖于硬件配置,有些环节依赖于操作系统,还有些环节依赖于系统中的默认库文件。总的来说,不管多复杂,目标只有一个,就是把源文件编译成目标机器可执行的可执行程序。

    2020-04-09 10:45:45

  • 小昭

    2022-09-12 22:19:27

    这节课看一遍不够
  • doge

    2020-02-16 16:54:11

    老师好,我用#define log(frm, args...) log(frm, args)的方式会报错expected expression before ‘)’ token,但用#define log(frm, ...) log(frm, ##__VA_ARGS__) 就不会有问题,这是为什么?
    作者回复

    你得应该是 #define log(frm, ...) printf(frm, ##__VA_ARGS__) 吧?

    因为后面一种实现方式,加入了 ## 宏连接符,所以允许变参列表中的内容为空。第一种没有 ## 连接符的,不允许内容为空。

    2020-02-22 19:54:30

  • 微风漂过

    2020-02-05 18:10:10

    #define P(item) printf("%s = %d\n", #item, item);
    #item前面的#,作用是什么?
    作者回复

    是把iterm原本要替换的内容变成对应的字符串。你可以自己写一个小的实验性程序,试一下#的作用。

    2020-02-06 01:57:47

  • 徐洲更

    2020-01-30 23:58:31

    __typeof(a) 用于获取a的变量类型
    C语言的变量要定义后才能用, swap宏是一个适用于多种数据结构的宏,因此需要根据具体变量声明具体的变量类型
    作者回复

    d(^_^o)没问题!

    2020-01-31 11:46:09

  • 阿阳

    2023-02-07 15:30:05

    这节课难度好大,感觉跟指针的难度相当。以前看C工程的源码,用到好多宏定义,这节课让人大开眼界。
  • 25ma

    2022-09-30 17:09:17

    c 语言定义宏如果有多行需要在 行尾 加上反斜杠
    ```
    #define log(frm, args...) {\
    printf("[%s : %d] ",__func__,__LINE__); \
    printf(frm, ##args); \
    }

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

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

    2022-03-13 16:52:12

    #include <stdio.h>
    #include "stdarg.h"
    #define log(frm, args...){\
    printf("[%s:%d]",__func__,__LINE__);\
    printf(frm, ##args);\
    }

    int main() {
    log("%s说:%s","neo", "hello world!");
    return 0;
    }


    ##的类似粘合剂,将frm和args粘合起来,一同作为printf的参数;
  • 涤生

    2020-11-01 10:38:06

    老师我用#define log(frm,…)printf(frm,__VA_ARGS__)报错__VA_ARGS__是未定义的标识符
  • 吕作晶

    2020-03-29 23:55:49

    因为有++这类运算符,使得宏定义基本上都不得不使用__typeof()~不知道为什么感觉把一个事情反而变得特别的麻烦~始终觉得带__的东西,可读性都很差~
    作者回复

    :-(没办法,如果是我定的标准,肯定把两个下划线去掉。(*^o^*)

    2020-03-30 11:19:49

  • 🤪HappyJoo

    2020-02-13 13:08:51

    #define MAX(x, y) ({\
    int _x = x;\
    int _y = y;\
    _x>_y ? _x : _y;\
    })//(a)>(b)?(a):(b)) 记得前后的字母要一致哦

    其实我对这个临时变量还是不太理解哈,希望老师能指教。我的理解是,用了临时变量当做替身,丢进去计算,再把结果直接返回。但是在赋值之前,该做的运算都做了,做好之后才赋值给临时变量的。++是在丢进去之前就计算好了,所以a先变成7,然后赋值给临时变量,再丢进去MAX运算得到结果。不然不知道怎么解释它只加了一次嘿嘿嘿。
    作者回复

    不是的,宏做的是简单的代码展开和替换,你把相应调用出的宏展开以后,你会看到 int _x = a++; 对于这段代码来说,a++只执行了一次。注意,宏不是函数,丢给宏的参数,宏也不会进行计算,宏做的就是简单的替换。

    2020-02-13 19:49:06

  • 潮汐

    2020-02-01 21:04:42

    看宏里的代码意图,是要获取参数a的类型(类型文本值,整型int,字符型char...)。可猜出__typeof(a)用于获取a的数据类型值。
    作者回复

    正确!d(^_^o)

    2020-02-02 10:55:38

  • 栾~龟虽寿!

    2020-01-30 22:25:08

    我认为log函数,有两个形参,我们只输入了一个,这个错误,对不?
    作者回复

    既然需求是做一个使用方式和printf一样的函数,printf支持单个参数的形式,log也应该支持。所以,传入一个参数没问题。

    2020-01-31 11:45:38

  • Aaron Shan

    2020-01-30 17:47:42

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    给所有变量加上括号,保证代入宏的每个变量都是相互独立
    作者回复

    已经很棒了,你的答案应该可以正确的输出样例中的前四行,后两行:
    P(MAX(a++, 5)); MAX(a++, 5) = 6
    P(a); --> a = 7
    你的这个方法就不行了。

    2020-01-30 22:17:31

  • Jinlee

    2020-01-30 17:08:56

    猜测__typeof(a)函数的作用是声明变量,因为我删掉这段代码后,编译:[Error] '__temp' undeclared (first use in this function)。“搞事情”没搞成 - _ -
    作者回复

    哈哈哈,猜测差不多正确。你可以吧 __typeof 看成是类型,就是 int 或者 double 或者是别的什么类型,具体是什么类型呢,根据 __typeof 括号中的表达式来决定。也就是说,其中 a 要是整型,那么 __typeof(a) 最终会被替换成为 int。

    2020-01-30 22:18:55

  • 学写代码的猪

    2020-01-30 00:45:05

    看完,睡觉。明天再消化!
    作者回复

    d(^_^o)

    2020-01-30 09:23:53