作用:为了避免同一个文件被include多次,C/C++中有两种方式,一种是#ifndef方式,一种是#pragma once方式。在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别
//方式一: #ifndef __SOMEFILE_H__ #define __SOMEFILE_H__ ... ... // 声明、定义语句 #endif // 方式二: #pragma once ... ... // 声明、定义语句
区别:
第一点ifndef是通过判断宏是否被定义,来决定该文件是否需要被编译器包含编译,一般宏的命名方式都是 _+文件名(大写)+_H_这样的形式,如果没有define,那么就define一次好了,记得之前写程序的时候很多时候都会忘记在最后加上#endif这个语句。有一个比较关键的地方就是这个宏千万不能再别处定义,如果不小心在其他地方定义了,那就很简单了,该文件没有被包含嘛,程序会出现变量未声明等错误。因为编译器在执行编译的时候都需要打开这个文件,然后判断该文件是否被define 过,如果是大型工程的话,编译过程就会很花费时间。而#pragma once 不同,它是编译器来提供保证,它的判断机制是物理上的文件是否相同,而不是内容相同的两个文件,这样就省去了重复打开文件操作。所以推荐使用#pragma once 这种写法。
第二点就是#ifndef 是c/c++的标准,所以走到哪里都会被支持,而#pragma once 是#ifndef 之后产生的 一些老的编译器可能不被支持。
下面举例说明:
如果定义了头文件A.h,B.h和源文件C.cpp。如果我们在A.h中写上一个函数,在B.h中include A.h,然后再在C.cpp中include A.h和B.h,这样我们就会出现重复包含的问题
//A.h #include <iostream> using namespace std; int add(int a, int b) { std::cout << "sub: a+b = " << a + b << std::endl; return a + b; }
//B.h #include <iostream> using namespace std; #include "A.h" int sub(int a, int b) { std::cout << "sub: a-b = " << a - b << std::endl; return a - b;
//C.cpp #include <iostream> using namespace std; #include "A.h" #include "B.h" int main(void) { add(1, 2); sub(4, 5); return 0; }
出现错误:错误 C2084 函数“int add(int,int)”已有主体
在A.h和B.h中加上#pragma once,就可以编译通过。
上面的#ifndef...#endif 和 #pragma once 并不能完全消除所有的重定义问题。如下代码:
// A.h #ifndef _A_H_ #define _A_H_ int a = 10; #include <iostream> using namespace std; int add(int a, int b) { std::cout << "sub: a+b = " << a + b << std::endl; return a + b; } #endif // _A_H_ // B.h #ifndef _B_H_ #define _B_H_ #include <iostream> using namespace std; //#include "A.h" int a = 5; int sub(int a, int b) { std::cout << "sub: a-b = " << a - b << std::endl; return a - b; } #endif // _B_H_
错误:“a"重定义,多次初始化:
上面出现了重定义的问题,这是什么原因呢?在C++静态变量中有三种类型,其中有一种外部链接型的静态变量。而全局变量在声明的时候没有加上修饰词static,因此这种变量是属于外部链接型的变量。而外部链接的变量对于在同一个工程文件的所有文件中都是可见的,所以在其中一个文件中定义的外部链接变量a,同时在另一个文件中也定义。或者在包含头文件的时候重复包含了某一个定义了外部变量的头文件,就会导致在同一个工程文件形成的程序中将包含两个一样的变量(同样函数定义也是一样会产生同样的错误)。
解决方法: 在.cpp文件中声明变量,然后建一个头文件(.h文件)在所有的变量声明前加上extern,注意这里不要对变量进行的初始化。然后在其他需要使用全局变量的.cpp文件中包含.h文件。编译器会为.cpp生成目标文件,然后链接时,如果该.cpp文件使用了全局变量,链接器就会链接到此.c文件 。
总结:在头文件的定义中,我们最好不要将函数定义和变量的声明放到头文件中。但是下面的几种确实头文件常包含的内容:
- 函数声明
- 类声明
- 使用const和#define定义的符号常量
- 结构体声明
- 模板声明
- 内联函数