C++头文件和源文件
参考文档: https://zhuanlan.zhihu.com/p/369949041
编译模式
C++编译规则:cpp文件在汇编时不需要知道其它cpp文件,使用其它cpp变量或函数时,会把变量和函数名放在符号表中,在链接阶段检查符号表。
C++的编译模式是分别编译。编译期间每个cpp并不需要知道其它cpp存在,只有到链接阶段,才会将汇编期间形成的每个obj链接成一个exe或out文件。
举个例子,在a.cpp中定义了一个变量a
,而在b.cpp只要向编译器承诺(extern 关键字),肯定有变量a
即可正常编译。在编译b.cpp时,编译器并不知道是不是真的有这个变量。
// a.cpp
int a = 10;
// b.cpp
extern int a;
int main()
{
cout << a << endl;
return 0;
}
在汇编阶段,编译器在收到承诺后(extern关键字),会留一手,它将生成一个符号表,把变量a
放到符号表中。
符号表的作用就是在链接阶段,去看程序员有没有兑现承诺(即其他文件中有没有变量a
定义),如果程序员对象承诺,则正常,若欺骗编译器,则编译器报错unresolved external symbol xxx
。
注意变量a
的声明可以有很多份,因为可能非常多的文件都用到变量a
,但定义只能有一份哦,否则编译器不知道以那份定义为准。
头文件
为什么需要头文件?头文件用来集中声明一些变量函数等。 头文件中只能放声明,但有三个例外。
书接上文,当很多地方都用到变量a
时,只需要都有声明即可,虽然可能会有非常多extern int a;
但也未尝不可。
但是如果这些地方还要用到变量b
,那也不算复杂,再将一个声明即可。
extern int a;
extern int b;
似乎也没有多麻烦。但是如果这些地方还要用变量c,d,e,f,g...
等几十个变量,那也要在每个地方都写几十个声明吗?
这样不仅写的很麻烦,而且维护也费力。设想如果要改一个变量名,那还是很打脑壳的。
为解决这个问题,头文件就派上用场。将所有声明写在头文件中,需要声明的时候,只要用#include "xxx"
即可。因为#include
只是简单的把包含的文件赋值粘贴过去,所以使用头文件和自己一个一个写声明的作用完全相同。
注意在头文件中不可有定义,例如下面这句话。
int a = 10; // 错误:头文件中不可有定义
extern int a; // 正确:头文件可有声明
为什么头文件不能有定义呢?因为可能多处包含这个头文件,若有定义则会出重复定义的错误。
头文件中不能有定义,有几个例外:
const
修饰的变量,是可以有定义的。- 内联函数是可以有定义的。
- 类是可以有定义的。
下一节详细解释。
可以有定义的头文件
头文件中可以有const
变量定义
在包含头文件的各个cpp中,每个cpp中的
const
变量都是不同的变量,所以不会造成重复定义。
默认const
修饰的全局变量是非extern
的(和static
修饰相同),作用域只在当前文件中。即如果头文件被两个cpp包含,这两个cpp就是两个编译单元,所以这两个cpp中的变量是两个完全不同的变量。
下面例子来说明这一点。
// test.h
const int a = 10;
// test1.cpp
#include "test.h"
void foo()
{
cout << &a << endl;
}
// test2.cpp
#include "test.h"
void foo(); // test1.cpp中的函数,test2.cpp也可调用,原理同上一节
int main()
{
foo();
cout << &a << endl;
}
结果如下:
00159B30
00159BD4
很明显test1.cpp和test2.cpp的变量a
的地址都不同,所以肯定是两个不同的变量。
正因为是两个变量,所以不会造成重复定义,这就是头文件中可以有const
定义的原因。
同理,static
修饰的自然也是可以有定义。
Tips1:下面这两句话等价,都是外部链接。
// test.h
int a = 10; // 默认前面有一个extern
extern int b = 100;
即,变量的作用域是整个程序。
Tips2: 如果在test.h中这样写:
//test.h
extern const int a = 10;
那么结果如何呢?会出现重复定义的错。因为默认const
是内部链接,加上extern
成外部链接了,外部链接作用域就成了全局,所以test1.cpp和test2.cpp产生冲突。
内联函数可以有定义
内联函数可以有定义的原因也类似,因为内联函数是内部链接。而非内联函数,其链接方式为外部链接。有资料解释说,内联函数与普通函数不同,内联函数是在目标位置直接展开,并非普通函数的先声明后链接。
本人觉得都有道理,这个**链接**为微软官方对内联的解释,可以作为不错的参照。
类可以有定义
对于下面这段代码,到底是类的定义还是声明网上一直没有准确的答案。
class Test
{
public:
Test();
private:
int a;
}
有人认为这是声明,有人认为这是定义。其实到底是什么倒不是特别紧要,不必过于学院派,只需要指导在头文件中可以这么写即可。
但个人认为这还是一个定义,这个**链接**为《JUMPING INTO C++》作者阐述到底什么是声明什么是定义,有条件的同学可以去阅读原文,关于类的部分我截图下来了,不想阅读原文的话可以直接看一下。
What it Means to Define Something in C and C++
Defining something means providing all of the necessary information to create that thing in its entirety. Defining a function means providing a function body; defining a class means giving all of the methods of the class and the fields. Once something is defined, that also counts as declaring it; so you can often both declare and define a function, class or variable at the same time. But you don’t have to.
For example, having a declaration is often good enough for the compiler. You can write code like this:
int func(); int main() { int x = func(); } int func() { return 2; }
Since the compiler knows the return value of func, and the number of arguments it takes, it can compile the call to func even though it doesn’t yet have the definition. In fact, the definition of the method func could go into another file!
You can also declare a class without defining it
class MyClass;
Code that needs to know the details of what is in MyClass can’t work–you can’t do this:
class MyClass; MyClass an_object; class MyClass { int _a_field; };
Because the compiler needs to know the size of the variable an_object, and it can’t do that from the declaration of MyClass; it needs the definition that shows up below.
如果这是定义,那么为什么允许类的定义在源文件中呢?
在这篇**文章** 中有一个浅显的解释,因本人不是学院派,故止步于此,若前辈又更深理解,则欢迎一起讨论。有条件同学可以阅读原文,我也将关键信息截图如下。
Putting class definitions in a header file
If you define a class inside a source (.cpp) file, that class is only usable within that particular source file. In larger programs, it’s common that we’ll want to use the classes we write in multiple source files.
In lesson 2.11 – Header files, you learned that you can put function declarations in a header files. Then you can #include those functions declarations into multiple code files (or even multiple projects). Classes are no different. A class definitions can be put in a header files, and then #included into any other files that want to use the class type.
Unlike functions, which only need a forward declaration to be used, the compiler typically needs to see the full definition of a class (or any program-defined type) in order for the type to be used. This is because the compiler needs to understand how members are declared in order to ensure they are used properly, and it needs to be able to calculate how large objects of that type are in order to instantiate them. So our header files usually contain the full definition of a class rather than just a forward declaration of the class.
主要因为两点:
- 方便应用在多文件多工程环境中。
- 传统上就是这样,类定义在头文件,类的函数声明在头文件,类函数定义在cpp中。
源文件
头文件不会参与编译。每个源文件都是一个编译单元。
头文件在预处理阶段就会被复制进源文件中,所以头文件并不会被编译,或者说,头文件会被放进源文件中,参与源文件的编译。
每个源文件都是一个编译单元,其将其包含的所有头文件一起进行编译,生成一个obj,待链接阶段将所有obj链接成exe或out(Linux)。