人人都能学会的编程入门课
胡光
原百度高级算法研发工程师
19410 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 38 讲
开篇词 (1讲)
人人都能学会的编程入门课
15
15
1.0x
00:00/00:00
登录|注册

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 文件包含预处理命令的作用。今天,我们将来着重讲解两种预处理命令宏定义条件编译。它们是什么意思呢?不要着急,听我一个个给你解释。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了预处理命令中的宏定义和条件编译两种重要概念。宏定义通过简单替换实现代码优化,但在复杂替换内容时可读性较差,文章介绍了在行尾加反斜杠的语法以提高可读性。同时,还提到了`__typeof`方法的作用。另外,文章详细介绍了条件编译的语法格式和指令,以及如何定义支持可变参数的log宏。最后,总结了宏定义的基本用法和条件编译的作用,强调了编译器预设的宏对代码可移植性的影响。整体而言,本文通过实例和图示生动地介绍了预处理命令的基本概念和技术特点,为读者提供了深入理解预处理命令的知识。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《人人都能学会的编程入门课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(20)

  • 最新
  • 精选
  • Aureliano
    最开始用#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-27
    6
  • Linuxer
    #define log(frm, args...) { \ // 原来这个地方就缺少了反斜线吧 printf("[%s : %d] ",__func__,__LINE__); \ printf(frm, ##args); \ } "##"的作用是对token进行连接,这里的args就是token,如果token为空,那么不进行连接,所以允许省略可变参数

    作者回复: 完美!

    2020-03-01
    5
  • 吕作晶
    _typeof()作用:声明和已知类型一样类型的新变量

    作者回复: yes!

    2020-03-29
    3
  • 罗耀龙@坐忘
    茶艺师学编程 我在这,卡了好久…… 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-01
    2
  • 1043
    \ 后面加了空格有办法一眼就看出来吗?换编程终端后面的黑白版颜色能看出来吗?宏所替换的计算功能也是先算乘除法后算加减法有括号先算括号吗?编译器预设的宏有标准和非标准的区别,是宏的状态是标准/非标准化,还是编译器是分标准/非标准化?比如同样用c语言编写,其中一份给Linux的gcc编译就是Linux的程序,用华为的方舟编译器编译完成后就是鸿蒙的程序了吗?

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

    2020-04-08
    2
  • doge
    老师好,我用#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-16
    1
  • 微风漂过
    #define P(item) printf("%s = %d\n", #item, item); #item前面的#,作用是什么?

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

    2020-02-05
    1
  • 徐洲更
    __typeof(a) 用于获取a的变量类型 C语言的变量要定义后才能用, swap宏是一个适用于多种数据结构的宏,因此需要根据具体变量声明具体的变量类型

    作者回复: d(^_^o)没问题!

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

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

    2020-03-29
  • 🤪HappyJoo
    #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
收起评论
显示
设置
留言
20
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部