本文已参与「新人创作礼」活动,一起开启掘金创作之路。
目录
引言
开始之前,先问大家一个小问题:
我们每次写完代码运行时,大家有没有想过我们的编译器是如何把.c文件转变成可执行文件(.exe)的呢?其实呢,一个.c文件变成一个.exe文件主要通过以下四个步骤:
我们今天只讲解预处理部分,其他的部分我们暂不关心。
1.预处理做的那些事儿
(1)头文件展开
我们都知道使用printf,scanf和malloc等等库函数,都需要引头文件(#include)。很多人知道要引,但不知道引的这个头文件里面有什么。
这里需要用到Linux系统。
我们写一段简易代码: (这边return打错了哈,但是不影响)
我们可以发现右下角的行号为6,我们预编译一下后,发现突然就多了八百多行代码,同时#include<stdio.h>也没有了,那么这些多出来的是什么呢?
我们向上翻看一下:找到了我们最熟悉的printf函数声明,简单翻阅,这时我们发现头文件展开后里面都是一些函数,类型声明等等。
所以,得到结论,头文件展开是在预处理阶段完成的。
(2)宏、数组宏常量替换
我们知道C语言中,我们可以通过#define定义数值宏常量(#define MAX 10)或者宏(#define DOUBLE(x) 2*x),而这些符号最后都会通过文本替换的方式替换我们的代码,而这个操作也在预处理的时候进行的。我们依然直接上代码测试:
替换后:
我们发现,确实是实打实的文本替换
宏定义补充
一:宏知识拓展
宏可以在源文件代码的任何地方定义
宏定义在源文件任何地方定义的都可以使用
宏替换只能替换其定义之后的(往后都是有效的)
执行代码(函数调用)滞后于宏替换
二:如何实现,编译器自己定义宏呢?
--Linux可以在命令行定义。
--vs中在属性栏(预处理器)定义
三:
宏定义复杂不止一条表达式时,可以封装成do while(0)语句。
宏定义中可以带有空格【# define M 10】但不推荐(只能带一个)。
(3)去注释
注释也同理,这里就不再演示了。有一点要说的是:注释在预处理替换时,本质是替换成空格。
补充:去注释和宏、数组宏常量替换的先后关系
同时在这里我有个问题想问大家:”预处理阶段去注释和宏替换谁先谁后呢?”相信很多人都没思考过这个问题,当然这个问题也不是那么重要,但作为深度剖析就得什么都顾及到,话不多说,直接上代码验证:
如果先去注释的化,最后两个printf函数应该都存在,如果先去预定义符号,那么只会保留第一个printf。来看结果:
这么一看结果显而易见了,刚刚问题也有了答案:
预处理阶段先去注释后替换预定义符号!!!
2.宏和函数的对比
学习完宏我们可以发现,宏和函数有很相似的地方。通过以下这组对比,我们能明确宏和函数的优劣:
优势:
1.简单计算时,例如加法器,宏定义更胜一筹(因为函数有调用,返回的消耗)
2.宏有参数类型限制,而宏没有,一些不同类型的计算更加方便
劣势:
1.如果宏体太长,导致多次替换使得代码过长。
2.可能存在运算符优先级问题(大多是括号问题)
3.没有类型检查,不严谨
4.不能递归
5.无法调试(因为调试在已编译后,才能运行调试,而宏替换在预处理已完成)
3.条件编译
什么是条件编译呢?其实这个和我们的if-else语句很相似,也很容易理解。
首先是
#ifdef,#if defined:判断宏是否被定义
#undef:取消宏定义
#if !defined 宏名,#ifndef :判断宏是否未被定义
#if,#elif,#else(这些都没什么好说的,和if-else类似)
#endif(限定范围,条件编译结束语句,这个很重要)
值得注意的是:#if (这类)不仅要判断宏有无定义,而且判断真假。(宏定义,但不给赋值就报错)
举个例子:
M为0,不输出;M为1,输出语句。
M不赋值,报错。
此外:
条件编译可以多条件集联(可以&&等等)
条件编译也支持嵌套(if嵌套一样)
说了这么多,所以条件编译有什么好处呢?
别急,它这就来了:通过代码裁剪,快速完成某种目的(版本维护,收费免费版区分,跨平台性)
本篇完~~