包括 .cpp 文件

80

我在像这样的地方看过,你必须包含.h文件而不是.cpp文件,否则就会出现错误。例如:

main.cpp

#include <iostream>
#include "foop.h"

int main(int argc, char *argv[])
{
int x=42;
std::cout << x <<std::endl;
std::cout << foo(x) << std::endl;
return 0;
}

foop.h

#ifndef FOOP_H
#define FOOP_H
int foo(int a);
#endif

foop.cpp

int foo(int a){
    return ++a;
}

代码可以正常运行,但如果我用 #include "foop.cpp" 替换 #include "foop.h" ,就会出现错误(使用 Dev C++ 4.9.9.2,Windows):

multiple definition of foo(int)
first defined here
为什么会这样呢?

这个例子应该可以编译通过。有任何遗漏的细节吗? - Mark Ransom
你是不是只包含了 .cpp 文件?在这种简单的情况下应该是可以的。同时包含 .h 和 .cpp 文件会导致函数被多次定义。 - Martin Beckett
10个回答

78

include指令的作用是将文件中<>""括起来的参数所对应的文件内容复制到当前文件中,因此当预处理器完成工作后,main.cpp看起来会像这样:

// iostream stuff

int foo(int a){
    return ++a;
}

int main(int argc, char *argv[])
{
   int x=42;
   std::cout << x <<std::endl;
   std::cout << foo(x) << std::endl;
   return 0;
}

因此,foo 将在 main.cpp 中被定义,但在 foop.cpp 中也存在一个定义,因此编译器会因为函数重复而“混淆”。


2
我想你可以通过使用 #pragma once 指令来解决这个问题。 - chtenb
4
@xorguy,foop.c和foop.h是如何知道彼此的?我理解你在回答中所说的内容,但我仍然不清楚main.cpp如何知道foo的定义。main.cpp包含了只有foo的声明而没有定义的foop.h,那么main.cpp如何知道foo的定义呢? 回答:@xorguy,foop.c和foop.h之间的联系是通过include语句建立的,它们可以相互访问对方的函数和变量。在这种情况下,main.cpp通过包含foop.h来获取foo的声明,而实际的定义是在foop.c中完成的,编译器会将两者合并以生成可执行文件。因此,main.cpp可以使用foo的定义而无需直接包含foop.c。 - Wakan Tanka
1
@WakanTanka 如果以正确的方式完成(即包括头文件),main.cpp 不会知道 foo.cpp,因此不会抛出错误。这就是为什么我们使用头文件的原因。在他的回答中,@xorguy 使用了一个例子,其中 main.cpp 代替 #including cpp 文件。在这种情况下,由于相同的函数最终在 main.cppfoo.cpp 中被定义,因此会引发错误。 - spicypumpkin
1
编译器会保持愉快,而链接器将显示有关重复实现的错误。同时,如果从链接中排除foo.cpp,则即使没有“#pragma once”,构建也将成功。但是,在大型项目中以这种方式进行操作会浪费编译器时间,因为所有代码都将在任何更改时重新编译。 - noonex

47

有很多原因不鼓励包含 .cpp 文件,但这并不是严格禁止的。你的示例应该可以正常编译。

问题可能是你在编译 main.cpp 和 foop.cpp,这意味着两个副本的 foop.cpp 被链接在一起。链接器会抱怨重复。


24
当你写 #include "foop.cpp" 时,就好像把整个 foop.cpp 的内容复制到了 main.cpp 中。
所以当你编译 main.cpp 时,编译器会生成一个包含两个函数(mainfoo)可执行代码的 main.obj
当你编译 foop.cpp 本身时,编译器会生成一个包含 foo 函数可执行代码的 foop.obj
当你将它们链接在一起时,编译器会看到两个函数 foo 的定义(一个来自 main.obj,另一个来自 foop.obj),并且会报告你有多个定义。

10

您只需要包含头文件。

如果您包含了头文件,头文件会自动查找相应的 .cpp 文件。 --> 这个过程由链接器完成。


2
这个答案完美地解释了问题所涉及的内容。 - pasha
3
@pasha 这个回答完全忽略了这个话题的细微差别,同时事实上也是错误的。 - enedil
2
@enedil,感谢您指出这一点,在谷歌上花了些时间后,我现在同意了。但请解释一下为什么是这样的,也许我可以学到其他的东西。 - pasha
6
我曾经阅读过这个回答,也认为它是正确的...但它是如此错误和有误导性。没有所谓的“头文件查找.cpp文件”的事情。因为只有.cpp文件被编译。头文件只是留在那里等待一些源文件包含它们。观看这个视频以了解更多信息:https://www.youtube.com/watch?v=3tIqpEmWMLI - Rick
@Rick 谢谢你提供的链接。 - pasha

8

这实际上涉及到了“定义”和“声明”的区别。

  • 您可以在不同的翻译单元或同一翻译单元中多次声明函数和变量。一旦声明了一个函数或变量,就可以从那个点开始使用它。
  • 您只能在所有翻译单元中定义非静态函数或变量一次。定义非静态项多次会导致链接器错误。

头文件通常包含声明;cpp文件包含定义。当您多次包含带有定义的文件时,链接时会出现冗余。

在您的情况下,一个定义来自于foo.cpp,另一个定义来自于包括foo.cppmain.cpp

注意:如果您将foo更改为static,则不会出现链接错误。尽管没有错误,但这并不是一个好做法。


这几乎是我正在寻找的答案。我在C++方面确实很新,那么例如如何声明一个类?我应该使用头文件吗?然后如何定义该类以在其他文件中使用?抱歉问这个无关的问题,但我找不到合适的帮助:( - user5066707
1
@TheProHands 看一下这篇文章 - Sergey Kalinichenko

2
由于One Definition Rule(可能是这个原因)。
在C++中,每个非内联对象和函数都必须在程序中有且仅有一个定义。通过在定义foo(int)的文件(CPP文件)中进行#include操作,它会在所有包含foop.cpp的文件以及foop.cpp本身中被定义(假设已编译foop.cpp)。
您可以将函数设置为inline以覆盖此行为,但我不建议在此处这样做。我从未见过需要或者甚至希望#include CPP文件的情况。
有些情况下,包含某些东西的定义是可取的。特别是当您尝试将模板的定义与其声明分离时,这种情况就更为真实。在这些情况下,我将文件命名为HPP而不是CPP以表示区别。

1: “(可能)”我在这里说“可能”,是因为您发布的实际代码应该可以编译而不出错,但考虑到编译器错误,似乎您发布的代码与您正在编译的代码并不完全相同。


1

由于你的程序现在包含了两个 foo 函数的副本,一个在 foo.cpp 中,另一个在 main.cpp 中

可以将 #include 看作是一条指令,告诉编译器将该文件的内容复制/粘贴到你的代码中,因此最终得到的处理过的 main.cpp 如下所示

#include <iostream> // actually you'll get the contents of the iostream header here, but I'm not going to include it!
int foo(int a){
    return ++a;
}

int main(int argc, char *argv[])
{
int x=42;
std::cout << x <<std::endl;
std::cout << foo(x) << std::endl;
return 0;
}

和 foo.cpp

int foo(int a){
    return ++a;
}

因此出现了多重定义错误。

1
main.cpp 中,**没有定义 foo()**,而 foo() 是在 foop.cpp 文件中定义的。当我们 #include<foop.cpp> 时,为什么 foo() 会被包含两次,而 foop.cpp 中的代码只被粘贴到 main.cpp 文件中 一次 - ajaysinghnegi

0
我想澄清一些事情:包含头文件并不是让链接器理解你的意图所必需的。你只需要声明它,它就可以被正确链接。

main.cpp:

#include <iostream.h>
//not including "foop.cpp"!

int foo(int a);

int main(){
  std::cout << foo(4) << std::endln;
}
  

foop.cpp:

int foo(int a){
  return a++;
}

我不鼓励这样做,但要知道头文件并不是必须遵循的魔法,以使代码编译。


0

我发现,如果你从Visual Studios进行编译,只需将包含的.cpp文件从最终构建中排除(即你要扩展的文件):

Visual Studios:.cpp文件 > 右键点击 > 属性 > 配置属性 > 常规 > 从构建中排除 >

我相信你也可以在命令行编译时排除该文件。


-3

使用“.h”方法更好 但如果你真的想包含.cpp文件,那么在foo.cpp中将foo(int)设置为静态的


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接