总结一下头文件的编写规则和注意事项:
-
头文件的作用是供其他的 .cpp 文件包含,它们本身不直接参与编译,但其内容会在多个 .cpp 文件中被编译。
-
头文件中应该只放变量和函数的声明,而不能放它们的定义。因为头文件的内容会被多个 .cpp 文件包含,如果放置定义,会导致同一个符号(变量或函数)在多个文件中被定义,违反了"定义只能有一次"的规则。
-
头文件中可以放置以下三种例外情况的定义:
- const 对象的定义:因为全局的 const 对象默认没有 extern 声明,所以它只在当前文件中有效,不会导致多重定义。
- 内联函数(inline)的定义:内联函数需要在编译时看到完整定义,且可以在程序中定义多次,只要在每个 .cpp 文件中只出现一次且定义相同。
- 类(class)的定义:创建类对象时,编译器需要知道类的完整定义才能确定对象的布局。可以把类的定义放在头文件中,函数成员的实现代码放在 .cpp 文件中,或者直接将函数成员的实现写进类定义中。
-
为了防止头文件被同一个 .cpp 文件多次包含导致的问题,可以使用头文件保护措施,即通过 #define 定义一个名字,并使用条件编译指令 #ifndef…#endif,让编译器根据这个名字是否被定义来决定是否继续编译头文件中的内容。
-
在编写头文件时,要特别注意使用头文件保护措施,以避免多重包含导致的编译错误。
总之,头文件的编写需要遵循一定的规则,合理划分声明和定义,并且要注意使用头文件保护措施。遵循这些规则和注意事项,可以有效避免头文件在多个 .cpp 文件中包含时导致的编译和链接错误。
当你在头文件中写下像 int a;
或 void f() {}
这样的语句时,实际上是在定义变量或函数,而不仅仅是声明。如果这个头文件被多个源文件包含,那么每个源文件都会有一个变量 a
或函数 f
的定义。在编译阶段,每个源文件都会独立编译,生成对应的目标文件(.o或.obj文件)。
问题出现在链接阶段。当链接器尝试将所有目标文件合并成一个可执行文件或库文件时,它会发现有多个 a
变量或 f
函数的定义。这违反了C++的一个规则:一个变量或函数只能有一个定义(One Definition Rule,ODR)。因此,链接器会报错,指出存在重复的符号定义。
为了避免这个问题,我们通常在头文件中只包含变量或函数的声明,而将它们的定义放在对应的源文件中。声明的作用是告诉编译器这个符号的存在,以及它的类型和签名等信息。而定义则提供了符号的实际内容或实现。
例如,在头文件中,我们可以写:
// MyHeader.h
extern int a;
void f();
然后在对应的源文件中提供定义:
// MySource.cpp
#include "MyHeader.h"int a = 42;
void f() {// 函数的实现
}
通过这种方式,我们可以在多个源文件中包含同一个头文件,而不会导致重复定义的错误。编译器会在编译每个源文件时使用头文件中的声明,而在链接阶段,链接器会找到变量 a
和函数 f
的唯一定义,并正确地生成最终的可执行文件或库文件。
这就是为什么我们通常遵循这样的规则:在头文件中只写声明,而在源文件中写定义。这有助于避免由于重复定义而导致的编译和链接错误。
参考链接:https://www.cnblogs.com/lidabo/archive/2012/04/17/2454568.html