1、条件编译
1.1 条件编译如何使用?
C语言提供的条件编译的功能可以让我们按照不同的条件去编译不同的程序部分,从而产生不同目标代码文件。
第一种形式:
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的功能是,如果标识符已经被 #define 定义了,则只会对程序段1进行编译,不会对程序段2进行编译,如果没有被定义则反之,如果我们不需要程序段2,也可以省去 #else 和他对应的程序段。
第二种形式:
#ifndef 标识符
程序段1
#else
程序段2
#endif
第二种形式与第一种形式的区别是将 ifdef 改为 ifndef,它的功能是,如果标识符没有被 #dfine 定义,则对程序段1进行编译,不会对程序段2进行编译,如果被定义了则反之, 如果我们不需要程序段2,也可以省去 #else 和他对应的程序段。
第三种形式:
#if 常量表达式
程序段1
#else
程序段2
#endif
第三种形式的功能是:如果常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译,因此可以使程序在不同条件下,完成不同的功能。
至于里面还可以添加 #elif 命令,意义与 else if 相同,形成一个 if else 阶梯状语句,可进行多种编译选择。
注意:如果定义空宏则会报错,因为 #if 后面必须要更常量表达式!
1.2 用 #if 模拟 #ifdef
此代码的意思是,如果 PRINT 宏被定义了,则执行第一个打印函数,否则执行第二个打印函数,同时我们也可以模拟 #ifndef,只需前面加个逻辑非就可以 ' ! ',例如:#if (!defined(PRINT))
就这样完了吗?其实并没有,在更复杂的项目中,往往会出现两个或多个宏需要同时定义才能满足需求,我举一个很简单的例子,如果我定义了 C 宏和 CPP 宏,我才可以编译所对应的代码:
如上代码就需要两个宏都被定义才能编译下面的程序段,相信学习过逻辑与的小伙伴应该很容易理解吧,那么我们如果需要两个都未定义才能编译下面的程序段呢?如何写?
两个都未定义才编译: #if (!defined(C) && !defined(CPP)) 前面分别加逻辑非就可以 ' ! '
或者:#if (!(defined(C) || defined(CPP))) 本代码中逻辑或只要有一个被定义,就为真,然后执行逻辑非,这样也能保证两个都未定义才进行编译!
至于最后用不用大括号给括起来,我的建议是括起来,这样我们阅读代码会更直观!
既然出现了逻辑与,是不是也可以出现逻辑或呢?当然上面已经有例子了,但是这里我就不一一演示了,感兴趣的可以下来自己去尝试一下。
条件编译支持嵌套:
这里其实和我们平常用的 if 嵌套式是似的,也很容易理解,这里我们就不细说,有一点要注意的就是,条件编译每个 #if 都需要有对应的 #endif 来结束
1.3 为何要有条件编译?
我们先对我们上面2小节的内容做一个总结:条件编译本质上是让编译器对代码进行裁剪!
本质认识:条件编译,其实就是编译器根据实际情况,对代码进行裁剪,而这里 “实际情况” ,取决于代码平台,代码本身的业务逻辑。
- 可以只保留当前最需要的代码逻辑,其他去掉,可以减少生成代码的大小
- 可以写出跨平台的代码,让一个具体业务,在不同平台编译的时候,可以有同样的表现
条件编译都用在哪些地方呢?
张三有个公司,公司有个项目,项目对应的软件又有专业版,免费版,精简版等等...
难道每个版本都对应着不同的代码吗?不是的,这样维护起来太麻烦了,其实所谓不同的版本,本质就是功能上的有和无,所以在技术层面上,为了更好的维护,当然可以使用条件编译,需要哪个版本,就是用条件编译裁剪就行。
著名的 Linux 内核,功能上,其实也是用条件编译进行功能裁剪的,用来满足不同平台的软件。
2、文件包含
2.1 #include 究竟干了什么?
我相信 #include 对于每个编程小伙伴来说都不陌生,很多人写 C 语言第一件事就是写上 #include 可能老师会告诉你们这是包含标准输入输出头文件,至于如何包含的,可能不会跟你讲。那今天我们就来通过预处理来看一看到底是如何包含的:
我们来写上一小段代码:
前面说过,预处理会将头文件展开,去注释,宏替换,条件编译等等
在 Linux 环境下我们可以执行命令:gcc -E test.c -o test.i 保留预处理之后的文件并命名为 test.i
为了更好的对比,我们执行 vim 命令模式下的 vs 指令:vs /sur/include/tdio.h 也就是打开标准输入输出的头文件:
看到预处理的结果之后,发现文件大小比我们实际代码要大得多!
结论:#include 本质是把头文件相关内容,拷贝到源文件中。