C++头文件包含有讲究
1、《Google C++ 编程风格指南》中关于头文件的包含规则:
1、基本顺序:C标准库 –> C++标准库 –> 第3方库的头文件 –> 自己工程的头文件。如果是cpp文件最先包含的是首选的头文件,即例如a.cpp文件中应该优先包含a.h,首选的头文件是为了减少隐藏依赖。
2、在包含头文件时应该加上头文件所在工程的文件夹名,即假如你有这样一个工程base,里面有一个logging.h,那么外部包含这个头文件应该这样写:
#include "base/logging.h",而不是#include "logging.h"
这样做的目的或好处主要有以下几点:
1、为了减少隐藏依赖,同时头文件和其实现文件匹配,应该先包含其首选项(即其对应的头文件)。
2、除了首选项外,遵循的是从一般到特殊的原则。不过《Google C++ 编程风格指南》的顺序:C标准库、C++标准库、其它库的头文件、你自己工程的头文件中漏了最前面的一项:操作系统级别的头文件,比如sys/types.h不能归入C标准库,而是Linux操作系统提供的SDK。因此我觉得更准确的说法应该是:OS SDK .h , C标准库、C++标准库、其它库的头文件、你自己工程的头文件。
3、之所以要将头文件所在的工程目录列出,作用应该是命名空间是一样的,就是为了区分不小心造成的文件重名。
2、《C++编程思想》中的不同观点:
《C++编程思想》P432提到:头文件被包含的顺序是从“最特殊到最一般”。顺序应该是这样的:
在本地目录的任何头文件 –> 所有“工具”头文件 –> 第3方库头文件 ->标准C++/C库头文件
这样做的目的是:
保证.h文件的组成部分不被它自身解析(parse),这可以避免潜在的使用错误。因为被自身解析缺乏明确提供的声明或定义。在.c文件的第一行包含.h 文件能确保所有对于构件的物理界面重要的内部信息块都在.h中,如果的确是缺少了某些信息块,一旦编译这个.c文件时就可以发现这个问题。
例如一下代码就会出现链接错误:
//MyMath.h文件定义如下: #pragma once double acos(double Num); //////////////////////////////////////////////////////////////////// //MyMath.cpp文件定义如下: double acos(double Num) { return 1.0; } //////////////////////////////////////////////////////////////////// //main.cpp文件定义如下: #include "MyMath.h" #include <stdio.h> #include <math.h> int _tmain(int argc, _TCHAR* argv[]) { double a = acos(0.5); return 0; }如果以上代码的头文件包含顺序做如下修改:
#include <stdio.h> #include <math.h> #include "MyMath.h"则编译通过了。在调试运行时main函数调用还是C标准库的函数acos,这是因为函数调用的顺序是按头文件的包含顺序来的,即自定义的acos函数被覆盖了。如果MyMath.h里包含了内联函数,则优先调用的是内联函数。
由此可见,《Google C++ 编程风格指南》和《C++编程思想》倡导的包含头文件的顺序各有优点,《Google C++ 编程风格指南》应该能大量减少隐藏的头文件依赖,而《C++编程思想》则很容易让你清楚知道你所定义的接口是否和系统库及第三方库发生冲突。
3、头文件包含对编译器预编译机制的影响
现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。但是我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西,如宏,预处理器(一般是以”#”开头的预处理指令。如:#include “xx.h” #define等),都要重新处理一遍。 编译器的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件,以提高编译速度。
所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(vs中的.pch文件),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码,甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。(注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大。)
要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch 文件),如mfc中的StdAfx.h就是一个预编译头文件,其包含内容如下, 这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。
#include <afxwin.h> // MFC core and standard components #include <afxext.h> // MFC extensions #include <afxdisp.h> // MFC Automation classes #include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls #include <afxcmn.h>同时,我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成预编译文件(.pch) 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include“Stdafx.h”。原因是理所当然的,我们仅仅是要它能够编译而已。
在vs中我们可以用/Yc编译开关来指定StdAfx.cpp来生成一个.pch文件,通过/Fp 编译开关来指定生成的pch文件的名字。打开project ->Setting->C/C++ 对话框。把Category指向Precompiled Header。在左边的树形视图里选择整个工程,Project Options(右下角的那个白的地方)可以看到 /Fp “debug/PCH.pch”,这就是指定生成的.pch文件的名字,默认的通常是 <工程名>.pch。然后,在左边的树形视图里选择 StdAfx.cpp,这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文件的可以有 YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件。
这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:
1)如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,包含你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end.
2)如果你把pch文件不小心丢了,需要重新生成一个pch文件,即重新编译stdafx.cpp(即指定/Yc的那个cpp文件)。