您在头文件 (.h) 中声明该函数。然后,该头文件将#include
在您需要使用该函数的任何其他文件中进行编辑。这满足了编译器,因为它知道函数的签名以及如何调用它。
然后在源 (.cpp) 文件中定义该函数。源文件包含它自己的头文件,因此当您编译它时,您最终会得到一个包含该函数的完整编译代码的目标文件。
然后,链接器将您调用该函数的代码的任何部分(即包含标头的文件)与该函数的实际代码链接起来。
编辑示例:
富.h:
#ifndef FOO_H
#define FOO_H
class Foo
{
public:
int fooFunction(double a);
};
#endif
Foo.cpp:
#include "Foo.h"
int Foo::fooFunction(double a)
{
// Function definition
return 1;
}
编译Foo.cpp
生成Foo.obj
其中包含fooFunction
. 到目前为止,一切都很好。
酒吧.h:
#ifndef BAR_H
#define BAR_H
#include "Foo.h"
class Bar
{
public:
void barFunction();
};
#endif
酒吧.cpp:
#include "Bar.h"
void Bar::barFunction()
{
Foo foo;
int returnValue = foo.fooFunction(2.0);
}
Bar.cpp
包括Bar.h
,而后者又包括Foo.h
. 因此,当Bar.cpp
进行预处理时,Foo::fooFunction
将在文件顶部插入声明。因此,当编译语句时int returnValue = foo.fooFunction(2.0);
,编译器知道如何发出要调用的机器指令,因为fooFunction
它知道返回值 ( int
) 的类型,并且知道参数的类型(adouble
和this
foo
目的)。因为没有提供函数定义,所以函数不会被内联(内联意味着函数的整个代码被复制到调用它的位置)。相反,使用指向函数内存地址的指针来调用它。因为正在使用指针,所以编译器并不关心定义——它只需要知道“我需要在内存位置 Y 使用参数 A 和 B 调用函数 X,并且我需要有一个int
大小的内存部分准备好存储返回值,我假设那个地址的代码知道如何执行函数”。但是,编译器无法提前知道该函数的地址是什么(因为函数定义存在于单独的.cpp
文件并将成为单独的编译作业 AKA 翻译单元的一部分)。
这就是链接器的作用。一旦所有的翻译单元都被编译(可以是任意顺序),链接器返回到编译后的代码,Bar.cpp
并通过填写现在编译的定义的地址将两者链接在一起。fooFunction
在它被调用的地方Bar.cpp
,从而使编译后的代码完全可执行。