一、预编译指令和预处理指令
大家在日常的开发中,经常听到几种说法,比如预编译指令,预处理指令,甚至还有其它的说法。其实从严格意义上讲,二者基本是一回事,预处理指令是C++中的标准术语而预编译指令是开发者的通俗的叫法,至于其它的说法大家可以认为是更小众的叫法。
预处理指令,是指所有以“#”开头的指令,常见的如#include,#define等等。预处理器指令是编译过程的第一阶段,由预处理器执行(所以叫预处理器指令),它主要是对源码进行文本级的操作,为后续的正式编译做准备。
二、预处理指令分类及例子
在日常的开发中,常见的预处理指令主要有以下几类:
1、包含文件
即#include,这是开发者最常见的用法,其主要目的是为了代码重用,避免重复声明相关对象。比如类似下面的情况等:
#include <iostream>
#include "a.cpp"
2、预定义宏应用
即使用#define进行常量、函数等的定义,其目的主要是为了重用代码、提高代码的可维护性等。但一定要明白,宏的使用只是简单的替换,内部有不少的坑坎,开发者要加强小心。
#define T 100
#define ADD(a,b) (a+b) //考虑有没有问题?
3、条件编译
条件编译算是C++开发者用得非常多的一种方法,主要目的是通过指令来处理代码是否参与编译的条件。比如一段代码功能的启用,头文件的重复包含,是否为调试模式以及其它条件,都可以使用,如:
#pragma once //避免头文件重复包含
//其下类似
#ifdef condition
code
#endif
#ifdef CPLUSPLUS
code
#else
code
#endif
4、编译器指令
这个用得也不少,一般用在一些特定的应用场景上。其主要目的是为了向编译器提供一些指令实现的手段,比如:
#pragma comment(lib,"*.lib")
#pragma pack(push, 1)
#pragma warning(disable: xxxx)
5、异常指令
这个就属于对一些编译异常的处理了,大部分开发者用得相对要少得多,类似下面:
#if !defined(__MAX__)
#error "value out of ranges!" // 中断编译
#endif
#if sizeof(long) != 8
#warning " 32-bit systems" // 警告
#endif
其实预处理器指令在不同的平台还有不少,而且有些还是平台专门定制的,大家可以根据相关的平台资料说明进行应用。
三、利用预处理器指令控制头文件
在大家常见的预处理器指令中,有一种重要的用法就是防止重复包含控制头文件(其实其它的类似文件基本类似)。一般来说,控制头文件重复包含有下面的两种简单用法:
//第一种
#pragma once
//第二种
#ifndef __DEMO_H__
#define __DEMO_H__
#endif
第一种方法在Windows平台上开发,非常常见,VS开发中自动生成的头文件,都会自动有此预处理器指令。但需要说明的是,这个指令并不是C++标准支持的,只不过目前主流的C++编译器都支持(这也是前面总提的各编译器厂商夹带的私货)。第二种情况在Linux上开发非常常见(当然,Windows平台上用得也不少),比如用Qt自动生成的头文件就是这种情况。
对于第二种的用法,非常好理解,这里简单介绍一下第一种(#pragma once)的内部实现。编译开始后,编译器会在内部维护一个列表,用来保存已经发现的头文件,即每次发现#pragma once,预处理器都会对这个列表进行处理。编译过程中,如果列表中有当前的头文件,则不再处理,这样就可以防止重复包含头件,否则,当前的头文件就会被追加到维护的列表中。
这两种方法各有优缺点:
1、第一种简单、方便和安全,缺点是非标准定义,有可能会被特定的编译平台不支持
2、第二种稍显复杂,且容易出现名字重复,产生一些莫名的编译问题,这个在大工程中可能会出现并不容易定位
3、第一种情况对一些异常情况(比如同文件但不同路径)产生异常问题,而第二种则不会
4、第一种情况一般比第二种情况编译速度可能会快些
5、第一种更容易维护,但处理复杂头文件包含时可能有所欠缺,第二种看上去略有些复杂,但基本适应各种场景
四、总结
学习和应用都是一个渐进的过程,大家不要担心这些东西不会用。只要一直坚持开发编码,这些知识点都会用到。正如前面反复提到的,技术和知识点都只是基础,重要的是在实践中不断的将这些技术和知识点融会贯通,灵活应用,从而更好的解决实践问题!
活学善用,才是根本。
C++头文件控制与预处理器指令


被折叠的 条评论
为什么被折叠?



