C++中的头文件
最近在写pi-slam-fusion,遇到的问题比较多,最离谱的问题可能就是和头文件有关的这部分了。
1. 关于为什么C++需要“在头文件中声明,在源文件中定义”?
当时为了把pislam和map2dfusion弄到一个程序里面,想出了#include “main.cpp” 和把 “main.cpp”重命名为”pislam.h”再进行#include的操作,这样肯定是有问题的!
实际上当时一编译就会跟我报“重定义”的错误,然后我居然还以为是CMakeList没写好
第一,预编译指令#include的作用是将所包含的文件全文复制到#include的位置,相当于是个展开为一个文件的宏。
c++的预处理器在编译之前执行,它看到#include指令,就会把那个文件的内容替换到当前位置。其它的预处理指令例如#define, #ifdef, ##等也在这个阶段被执行、并产生相应的内容。
预处理器执行完成后,所有的预处理指令都会被移除。其结果是一个单个头的大文件(我猜测这文件只存在于内存里),这个文件才会被进一步传给编译器做编译。
第二,C++允许多次声明,但只允许一次实现。比如int foo();就是一次声明,而int foo(){}就是一次实现。
如果编译时有多个.cpp文件中#include了同一个含有函数实现的.h,这时候链接器就会在多个目标文件中找到这个函数的实现,而这在C++中是不允许的,此时就会引爆LNK1169错误:找到一个或多个重定义的符号。
因此为了让函数可以在各个.cpp中共享,正确的做法就是在.h中只声明函数,并在另【 一个(重点)】.cpp中实现这个函数。这样就不会冲突了。
所以#include一个.cpp文件也是不可以的,这和#include一个含有函数实现的.h文件效果一样糟糕。
2. 头文件中应该写什么内容?
因为问题一的存在,现在我其实对头文件里面应该写什么也不太清楚了,所以又查了一些内容。。。
头文件中应该只放变量和函数的声明,而不能放它们的定义。不过这样的规则有三个例外:
头文件中可以写 const 对象的定义。
因为全局的 const 对象默认是没有 extern 的声明的,所以它只在当前文件中有效。
同理,static 对象的定义也可以放进头文件。
头文件中可以写内联函数(inline)的定义。
C++ 规定,内联函数可以在程序中定义多次,只要内联函数在一个 .cpp 文件中只出现一次,并且在所有的 .cpp 文件中,这个内联函数的定义是一样的,就能通过编译。
那么显然,把内联函数的定义放进一个头文件中是非常明智的做法。
头文件中可以写类(class)的定义。
编译器只有在这个类的定义完全可见的情况下,才能知道这个类的对象应该如何布局,所以,关于类的定义的要求,跟内联函数是基本一样的。
我们可以把类的定义放在头文件中,而把函数成员的实现代码放在一个 .cpp 文件中。
也可以直接把函数成员的实现代码也写进类定义里面。在 C++ 的类中,如果函数成员在类的定义体中被定义,那么编译器会视这个函数为内联的。因此,把函数成员的定义写进类定义体,一起放进头文件中,是合法的。
注意一下,如果把函数成员的定义写在类定义的头文件中,而没有写进类定义中,这是不合法的,因为这个函数成员此时就不是内联的了。一旦头文件被两个或两个以上的 .cpp 文件包含,这个函数成员就被重定义了。
头文件中的保护措施:
设想一下,如果 a.h 中含有类 A 的定义,b.h 中含有类 B 的定义,由于类B的定义依赖了类 A,所以 b.h 中也 #include了a.h。现在有一个源文件,它同时用到了类A和类B,于是程序员在这个源文件中既把 a.h 包含进来了,也把 b.h 包含进来了。这时,问题就来了:类A的定义在这个源文件中出现了两次!于是整个程序就不能通过编译了。
使用 “#define” 配合条件编译可以很好地解决这个问题。在一个头文件中,通过 #define 定义一个名字,并且通过条件编译 #ifndef…#endif 使得编译器可以根据这个名字是否被定义,再决定要不要继续编译该头文中后续的内容。这个方法虽然简单,但是写头文件时一定记得写进去。
综上所述
.h文件中能包含:
- 类成员数据的声明,但不能赋值
- 类静态数据成员的定义和赋值,但不建议,只是个声明就好。
- 类的成员函数的声明
- 非类成员函数的声明
- 常数的定义:如:constint a=5;
- 静态函数的定义
- 类的内联函数的定义
不能包含:
- 所有非静态变量(不是类的数据成员)的声明
- 默认命名空间声明不要放在头文件,using namespace std;等应放在.cpp中,在 .h 文件中使用 std::string