这里涉及到了#ifndef
、#define
、#endif
,我们先来回忆一下它们三个有关的知识
1.预处理指令
常用的预处理指令有三种:
- ==宏定义==指令:#define
- ==文件包含==指令:#include
- ==条件编译==指令:#if、#elif、#else、#ifdef、#ifndef、#endif
这里我们用到的#ifndef
虽然是宏定义的一种,但其实更确切的说应该是属于条件编译
2.条件编译是什么
条件编译是指==预处理器==根据相关的==条件编译指令==,选择性将源代码中的==部分==代码送到==编译器==进行编译。
我们来设想一下,如果我们有一套代码,既能运行在windows上,也能运行在linux上,那么这套代码的自定义头文件中,就应该包含适用于两套操作系统的头文件。
当我们把这套代码运行在linux上时,如果直接全都编译了,有一部分适用于windows的头文件这个时候就是无用的代码,只会==增加内存开销==,==降低程序的效率==
性能是对硬件说的,指的是硬件(如CPU,显卡)的运算能力强还是弱。这个理解起来很简单就不多说。
效率是对软件说的,指的是软件(如操作系统,应用程序软件)的算法以及编程质量的好坏,一般也叫执行效率,比如说我们2个人编程,计算公式N(N-1)(N-2)的值,但是两个人编写的程序不是完全一样的,因此执行同一运算的时间也有所差别,时间短的我们一般都会认为更好了(事实上未必),这就是效率。
3.条件编译的作用
条件编译就是用来应对这种情况的,条件编译的两个作用:
- ==提高程序的灵活性和可移植性==:使用条件编译,可以按照条件,==选择性==省略一部分无用代码,生成不同的目标文件
- ==避免头文件重复引用的问题(重要)==:头文件可能存在==相互包含==的关系,如果不预先处理,编译器会报错
4.条件编译指令以及对应的含义
条件编译指令有如下几个:
指令 | 含义 |
---|---|
#if | 如果满足条件,执行这部分的代码 |
#elif | 【否则如果含义】,如果上面的条件不满足且 #elif 的条件,才执行这部分代码 |
#else | 【否则含义】,如果上面的条件不满足,执行这部分代码 |
#ifdef | 如果已经定义了宏,执行这部分代码 |
#ifndef | 如果没有定义宏,执行这部分代码 |
#endif | 条件编译结束指令 |
5.条件编译指令的使用格式
1.常规使用格式#if-#elif-#else-#endif
#include <stdio.h>#define AA 0#define BB 1int main() {#if AA printf("1");#elif BB printf("2");#else printf("3");#endif return 0;}// 运行结果:2
这种格式有点类似于判断结构中的 if-else 条件语句,但又有着本质的区别。
一个是==预处理指令==,一个是==编译语句==,==执行的阶段不同==,编译器==要处理的代码量也不一样==。
2.删除已定义的宏#ifdef-#endif
#include <stdio.h>#define AA 123;#ifdef AA#undef AA#endifint main() { int AA = 33; printf("%d", AA); return 0;}// 运行结果:33
#ifdef 是用来判断程序中是否已经定义了宏。如果已经定义了,那么就==执行 #ifdef 到 #endif 之间的内容==;没有定义,那么==跳过这个内容==。
例如:我想在下面的代码中使用 AA 这个标识符,但我==不确定==前面的内容(引用的头文件里面)中==是否已经定义过==标识符为 AA 的宏。
那我就可以用这个方法进行判断,如果已经定义了 AA 这个宏,那就删除这个宏名,解放出标识符。
3.检测头文件是否被重复引用#ifndef-#endif
#include <stdio.h>#ifndef PI#define PI 3.1415#endifint main() { printf("%f", PI); return 0;}// 运行结果:3.1415
#ifndef 用来判断,程序中是否==没有定义==名字为某个标识符的宏。
如果没有,那就执行 #ifndef 到 #endif 之间的内容;
如果已经定义了,那么就会跳过这个部分的内容。
这个方法最常用在==头文件==中,用来检测==头文件是否被重复引用==的问题。
头文件重复引的例子:
1.头文件 h1.h 的代码如下:
#include "h2.h"
2.头文件 h2.h 的代码如下:
#include "h1.h"
3.主体代码文件 main.cpp 的代码如下:
#include "h1.h"#include "h2.h"int main() { return 0;}// 运行结果:报错,头文件重复包含
解决办法
在相关的头文件里面,添加#ifndef-#endif
条件编译,就可以避免头文件重复包含这种问题
新的 h1.h 代码如下:
#ifndef _H1_H_#define _H1_H_#include "h2.h"#endif
新的 h2.h 代码如下:
#ifndef _H2_H_#define _H2_H_#include "h1.h"#endif
注意要写#endif
所有的==条件编译==格式,最后都要有一个#endif
,这个指令表示==条件编译已经结束了==,如果没有这个指令,就会报错
6.条件编译与条件语句的区别
1.运行阶段
- 条件编译:属于==预处理指令==, 而预处理指令相当于是==文本替换==的作用,在==预处理阶段==,条件编译就将==满足条件的代码==送到==编译器==进行编译
- 条件语句:属于==语句==,是在编译后,==程序运行时==才开始执行判断
生效的阶段 | 效果 | |
---|---|---|
条件编译 | 预处理阶段 | 将满足条件的代码送到编译器进行编译 |
条件语句 | 程序运行时 | 执行判断 |
2.编译的代码量
条件编译与条件语句要编译的代码量是不同的,
- 条件编译:只会将满足条件的部分代码送入编译器,而不满足条件的代码会被清理掉
- 条件语句:全部代码都要进入编译器
1 | 要编译的代码量 |
---|---|
条件编译 | 满足条件的部分代码,不满足条件的代码会被清理掉 |
条件语句 | 全部代码 |
假设:有2000行代码,其中1000行与windows有关,1000行与linux有关,当我们把这些代码运行到linux上时,只有1000行与linux有关的代码是有效的,
如果使用条件语句,这2000行代码都会进入编译器进行编译,然后再进行判断;
如果使用条件编译,只会有1000行与linux有关的代码被送到编译器进行编译,可以==节省编译时间==,==减少内存开销==