据我所知,在头文件中提供定义允许其他文件引用它们,然后链接器将所有目标文件添加到一起并在以后提供定义。
这是为了让我们可以重用其他库中的其他实现吗?
如果我们没有使用头文件或滥用它们并将所有代码放入其中,即完全实现会发生什么?编译每个文件需要更长的时间,因为需要在对等文件的基础上编译完整的定义吗?它是否会导致链接器出现问题,因为它是相同实现的多个编译版本?
这如何与模板一起使用?
布莱尔
答案 0 :(得分:2)
每次新的编译目标包含该头文件时,头文件中的任何代码都会被编译,所以至少是的,编译需要更长的时间。但这并不是一个重要的问题。
让我们说你有这个标题:
int f_header() {
return 0;
}
这些源文件:
/* a.cpp */
#include "myheader.h"
int f_a() {
return f_header();
}
/* b.cpp */
#include "myheader.h"
int f_b() {
return f_header();
}
现在,当您编译这些源中的每一个时,编译将成功。但是,当您链接程序时,链接器将失败,因为您将提供函数f_header()
两次。
模板是一种特殊情况,因为它们实际上不是代码,而是为基于不同类型生成的代码提供模板。虽然当多个源文件使用相同类型时会出现重复,但编译器能够为您处理这种特殊情况。
答案 1 :(得分:0)
在源文件和头文件中拆分代码或多或少是历史上需要的。它允许编译器分别编译大项目的代码单元,整个项目太大而不能一次编译。
它允许缓存未更改的编译单元,这进一步缩短了编译时间。
从技术上讲,编译器会看到一整套不同的编译单元。如果您提供相同定义的多个实现,则编译器无法告诉您。但是,一旦将编译后的单元包装到单个可执行文件中,链接器就会偶然发现它。
今天,像C#或Java这样的“现代”语言已经远离这种历史限制。但是C ++是一种复杂的语言,即使对于编译器来说也是如此,因此这些优点在某种程度上仍然具有相关性。
答案 2 :(得分:0)
编译器一次编译一个文件。编译器正在解析的源文件可能包含其他文件,但所有这些包含的文件在预编译阶段“扁平”为单个源,并且编译器“看到”一个单独的代码实体。
由于上述原因,如果代码在.cpp或.h文件中,编译器无关紧要,因为编译器没有进行区分。但是如果某个函数定义或某个变量定义在.h文件中,那么问题就会出现在链接阶段。
链接器没有看到源文件,但确实看到了目标文件(.obj),它们是编译器生成的结果。链接器知道每个目标文件中的每个函数和每个变量,并且链接器作业是将它们全部绑定(或链接)在一个可执行文件中。如果在不同的目标文件中有多个具有相同名称的变量或函数,则链接器将无法使用它们,并且它将声明错误。可以通过选项强制链接器忽略所有额外的变量和函数,但不能轻易使用该选项。具有相同名称的两个函数实际上可能完全不同,忽略其中一个函数将导致各种错误。
另一方面,模板根本不是功能。它们是实际功能的蓝图。编译器遇到模板时,不会发生任何实质性的事情。只有当模板实例化为实际函数时,编译器才会生成这样的函数。因为这个模板可以很好地驻留在头文件中。