嗯...从哪里开始...
在其他语言中发生在幕后的事情在 C++ 中更为明显。从 C++ 获取二进制文件(例如可执行文件)的过程包括首先编译源代码(其中有子步骤,但编译器会处理它们)以获取目标文件,然后由链接器链接目标文件以生成一个二进制文件。
理论上,您可以简单地 #include 一个项目中的所有 cpp 文件,然后将它们全部编译并“链接”(虽然没有什么可链接的)但这需要很长时间,更重要的是,在可能会耗尽编译器可用内存的复杂项目。
因此,我们将项目拆分为编译单元,按照惯例,.cpp 文件代表单个编译单元。编译单元是项目中经过编译以生成一个目标文件的部分。尽管编译单元是分开编译的,但它们之间必须有一些代码是通用的,这样每个编译单元中的一段代码才能使用其他代码实现的功能。 .h 文件通常用于此目的。事情基本上是在其中声明(有点宣布)的,因此每个编译单元都知道当它是生成二进制文件的链接过程的一部分时会发生什么。
还有图书馆的问题。你可以在图书馆里找到两种主要的东西;
- 已经实现的功能,以二进制文件的形式提供给您,包括几乎可以运行的 CPU 指令(但它们必须插入正确的位置)。此表单附有 .h 文件,让您的 .cpp 文件知道库中的内容。
- 第二种是直接在 .h 中实现的功能
文件。是的,这在特殊情况下是可能的。有案例,
实施必须(弱者必须)伴随
声明(内联函数、模板类型等)。
第一种类型有两种形式:一个“静态库”(Windows 中的 .lib,Linux 中的 .a),它进入您的可执行文件并在链接期间成为它的一部分,以及一个“动态库”,即暴露给你的二进制文件(所以它知道),但这并没有成为它的一部分。因此,您的可执行文件将在运行时查找该动态库(Windows 中的 .dll 文件和 linux f.x 中的 .so 文件)。
因此,为了让您的 .cpp 文件能够接收来自库的服务,他们必须 #include 他们的 .h 文件,以了解其中的内容。稍后,在链接期间,您必须向链接器显示在哪里(文件系统中的路径)以找到这些库的二进制组件。最后,如果库是动态的,则 .dll(或 .so 等)必须在运行时可访问(例如,将它们保存在同一文件夹中)。
在编译编译单元时,您必须告诉编译器在哪里可以找到 .h 文件。否则,它将看到的只是#include <something.h>,并且它不知道在哪里可以找到该文件。使用 gcc,您可以使用 -I 选项告诉编译器。请注意,您只需告诉文件夹。同样重要的是,如果 include 指令看起来像 #include<somefolder/somefile.h>,则不应在路径中包含 somefolder。所以调用看起来像:
g++ mycompilationunit.cpp -IPATH/TO/THE/INCLUDED/FILES -IPATH/TO/OTHER/INCLUDED/FILES -c
-c 选项告诉编译器它不应该尝试仅从该编译单元生成可执行文件,因此它会创建一个 .o 文件,以便稍后与其他文件链接。由于我们没有告诉它输出文件名,它会吐出mycompilationunit.o。
现在我们想要生成我们的二进制文件(您可能想要一个可执行文件,但您也可能想要创建自己的库)。所以我们必须告诉链接器进入二进制文件的所有内容。所有目标文件以及所有静态和动态库。所以,我们说:(注意这里的 g++ 也充当了链接器)
g++ objectfile1.o objectfile2.o objectfile3.o -LPATH/TO/LIBRARY/BINARIES -llibrary1 -llibrary2 -o myexecutable
这里,-L 选项在示例中是不言自明的。 -l 选项告诉要查找哪些二进制文件。如果在路径上找到静态库和动态库,链接器将同时接受它们,如果两者都找到,它将选择一个。请注意, -l 之后的不是完整的二进制名称。例如,在 linux 中,库名称采用 liblibrary.so.0 的形式,但在链接器命令中它们被称为 -llibrary。 finally -o 告诉编译器给你的可执行文件起什么名字。您需要一些其他选项来 f.x。创建一个动态库,但您现在可能不需要了解它们。