如果您定义了一个用于做某事的类,那么使用它所需要做的就是包含一个包含其方法原型的头文件(.h 文件),但您不必包含类文件本身( .cpp 文件,其中包含类方法的实际实现),
这是否意味着 .cpp 文件必须具有与其头文件相同的名称,以便编译器匹配名称以定位 .cpp 文件?
或者
当编译器遇到函数(方法)调用时,它会转到头文件并查看它是否具有原型。然后它在其内置函数中搜索实现,如果没有找到它在当前目录中的所有 .cpp 文件中搜索?
还是两个假设都不正确?
如果您定义了一个用于做某事的类,那么使用它所需要做的就是包含一个包含其方法原型的头文件(.h 文件),但您不必包含类文件本身( .cpp 文件,其中包含类方法的实际实现),
这是否意味着 .cpp 文件必须具有与其头文件相同的名称,以便编译器匹配名称以定位 .cpp 文件?
或者
当编译器遇到函数(方法)调用时,它会转到头文件并查看它是否具有原型。然后它在其内置函数中搜索实现,如果没有找到它在当前目录中的所有 .cpp 文件中搜索?
还是两个假设都不正确?
文件名在 C++ 中是无关紧要的。链接器将所有部分放在一起。在编译一些 C++ 代码时,您需要知道的只是事物名称的含义。这就是声明和类定义的目的:它们引入名称,从而为编译器提供足够的信息来生成代码:
文件 1.cpp:
int f(int, bool); // declaration
class Foo; // class declaration
class Bar { int g(); }; // class definition (implies declaration)
int main()
{
Foo * p; // OK, Foo * is a complete type, since we know that
// "Foo" denotes a class
// p->h(); // Error: We don't know what Foo actually is!
Bar b; // OK, we know how size and alignment for Bar
int m = b.g(); // OK, we know what sort of function B::g() is
int n = f(50, true); // OK, we don't need to know what f does
return m + n;
}
编译上述代码时,它将包含和的未解析符号。只要一个(并且只有一个)其他翻译单元包含这些符号的定义,链接器就可以解析它们并且程序可以成功链接:f
B::g
文件 2.cpp:
int f(int n, bool b) { return b ? n * 2 : 50 - n; }
// include class definition here
int Bar::g() { return f(sizeof(Bar), sizeof(int) == sizeof(char)); }
基本规则是,你可以声明一个函数或一个类,或者定义一个类a 任意多次,但你只能定义一个函数和一个类成员函数一次。有一个例外:如果您将类成员函数的函数声明为inline
,那么您实际上可以重复定义它,前提是所有定义都相同。
这条规则的推论是,您可以安全地将所有函数声明和类定义放入头文件并重复包含它,只要您在一个翻译单元中只提供一次实际定义。(并且该inline
规则允许您在类定义中包含实际的成员函数定义——与声明一起定义的成员函数是隐式的inline
。)
编译器只需要头文件。但是所有的 C++ 文件都需要编译,因此链接器可以生成最终的二进制文件。
为了生成程序,编译器需要两者。通常,从 C++ 到二进制文件的编译分两步进行,首先将每个 CPP 文件(也称为翻译单元)转换为目标文件(主要包含机器代码,但也包含地址表)。然后,目标文件相互连接(链接)(需要地址表),形成最终的可执行文件。通常,在头文件中,您会说“有一个带有这些参数和这个返回值的函数”。在目标文件中,对该函数的调用被记录为对该函数的引用。最后,在链接时,这个引用被解析为必须由其他目标文件提供的函数的实际地址。