ial*_*alm 132 c++ header-files
所以我完成了我的第一个C++编程任务,并获得了我的成绩.但根据评分,我失去了分数including cpp files instead of compiling and linking them.我不清楚这意味着什么.
回顾一下我的代码,我选择不为我的类创建头文件,但是在cpp文件中做了所有事情(它看起来没有头文件......).我猜这个评分者意味着我写了'#include'mycppfile.cpp";' 在我的一些文件中.
我#include对cpp文件的推理是: - 应该进入头文件的所有东西都在我的cpp文件中,所以我假装它就像一个头文件 - 在猴子看猴子做时尚,我看到了其他头文件#include在文件中是'd,所以我为我的cpp文件做了同样的事情.
那究竟是我做错了什么,为什么不好呢?
gol*_*udo 160
据我所知,C++标准在头文件和源文件之间没有区别.就语言而言,任何带有合法代码的文本文件都与其他任何文本文件相同.但是,虽然不是非法的,但是将源文件包含到程序中几乎可以消除您从一开始就分离源文件所带来的任何好处.
本质上,#include告诉预处理器获取您指定的整个文件,并在编译器获得它之前将其复制到活动文件中.因此,当您将项目中的所有源文件包含在一起时,您所做的事情和创建一个没有任何分离的巨大源文件之间基本上没有区别.
"哦,这没什么大不了的.如果它运行,那很好,"我听到你哭了.从某种意义上说,你是对的.但是现在你正在处理一个微小的小程序,以及一个很好且相对无阻碍的CPU来为你编译它.你不会总是这么幸运.
如果您曾经深入研究严肃的计算机编程领域,您将会看到项目的行数可以达到数百万而不是数十.那是很多台词.如果您尝试在现代台式计算机上编译其中一个,则可能需要几个小时而不是几秒钟.
"哦不!这听起来很可怕!但是我可以防止这种可怕的命运吗?!" 不幸的是,你无能为力.如果编译需要数小时,则编译需要数小时.但这只是第一次真正重要 - 一旦你编译了一次,就没有理由再次编译它.
除非你改变了什么.
现在,如果你有两百万行代码合并成一个庞大的庞然大物,并且需要做一个简单的错误修复,比如说x = y + 1,这意味着你必须再次编译所有两百万行来测试它.如果你发现你打算做一个x = y - 1代替,那么再过两百万行编译等着你.这浪费了很多时间,可以更好地做其他事情.
"但我讨厌没有效果!如果只有某种方法可以单独编译我的代码库的不同部分,并在某种程度上将它们连接在一起!" 理论上是一个很好的主意.但是如果你的程序需要知道不同文件中发生了什么呢?除非你想要运行一堆微小的.exe文件,否则不可能完全分离你的代码库.
"但肯定一定是可能的!编程听起来像是纯粹的折磨!如果我找到一些方法将接口与实现分开怎么办?假设从这些不同的代码段中获取足够的信息以将其识别到程序的其余部分,并放置相反,它们在某种头文件中?这样,我可以使用#include 预处理器指令只引入编译所需的信息!"
嗯.你可能会在那里做点什么.让我知道这对你有什么影响.
小智 43
这可能是一个比你想要的更详细的答案,但我认为一个合适的解释是合理的.
在C和C++中,一个源文件被定义为一个转换单元.按照惯例,头文件包含函数声明,类型定义和类定义.实际的功能实现驻留在翻译单元中,即.cpp文件.
这背后的想法是函数和类/结构成员函数被编译和汇编一次,然后其他函数可以从一个地方调用该代码而不会重复.您的函数原型隐式声明为"extern".
/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);
/* function body, or function definition. */
int add(int a, int b)
{
return a + b;
}
Run Code Online (Sandbox Code Playgroud)
如果希望函数对于翻译单元是本地的,则将其定义为"静态".这是什么意思?这意味着如果您包含具有extern函数的源文件,您将获得重定义错误,因为编译器不止一次地遇到相同的实现.因此,您希望所有翻译单元都能看到函数原型而不是函数体.
那么这一切最终如何被捣碎在一起呢?这是链接器的工作.链接器读取汇编程序阶段生成的所有目标文件并解析符号.正如我之前所说,符号只是一个名称.例如,变量或函数的名称.当调用函数或声明类型的转换单元不知道这些函数或类型的实现时,这些符号被认为是未解析的.链接器通过连接保存未定义符号的转换单元和包含该实现的转换单元来解析未解析的符号.唷.对于所有外部可见符号都是如此,无论它们是在代码中实现还是由其他库提供.库实际上只是一个包含可重用代码的存档.
有两个值得注意的例外.首先,如果你有一个小功能,你可以使它内联.这意味着生成的机器代码不会生成外部函数调用,而是按字面顺序连接.由于它们通常很小,所以尺寸开销并不重要.你可以想象它们在工作方式上是静态的.因此,在头文件中实现内联函数是安全的.类或结构定义中的函数实现通常也由编译器自动内联.
另一个例外是模板.由于编译器在实例化时需要查看整个模板类型定义,因此无法将实现与定义分离,就像独立函数或普通类一样.嗯,也许现在可能这样,但是对"export"关键字的广泛编译器支持需要很长时间.因此,如果不支持"导出",翻译单元将获得自己的实例化模板化类型和函数的本地副本,类似于内联函数的工作方式.由于支持"出口",情况并非如此.
对于这两个例外,有些人发现将内联函数,模板化函数和模板化类型的实现放在.cpp文件中"更好",然后#include .cpp文件.这是标头还是源文件并不重要; 预处理器不关心,只是一个约定.
从C++代码(几个文件)到最终可执行文件的整个过程的快速摘要:
同样,这比你要求的要多得多,但我希望这些细节可以帮助你看到更大的图景.
典型的解决方案是.h仅使用文件进行声明,使用.cpp文件进行实现.如果需要重用实现,则将相应的.h文件包含在.cpp必要的类/函数/使用的文件中,并链接到已编译的.cpp文件(.obj文件 - 通常在一个项目中使用 - 或.lib文件 - 通常使用从多个项目中重用).这样,如果仅实现更改,则无需重新编译所有内容.
头文件通常包含函数/类的声明,而.cpp文件包含实际的实现.在编译时,每个.cpp文件都被编译成一个目标文件(通常是扩展名.o),链接器将各种目标文件组合成最终的可执行文件.链接过程通常比编译快得多.
这种分离的好处:如果要重新编译项目中的一个.cpp文件,则不必重新编译所有其他文件.您只需为该特定.cpp文件创建新的目标文件.编译器不必查看其他.cpp文件.但是,如果要调用当前.cpp文件中的函数,这些函数是在其他.cpp文件中实现的,则必须告诉编译器它们采用了哪些参数; 这是包含头文件的目的.
缺点:编译给定的.cpp文件时,编译器无法"看到"其他.cpp文件中的内容.因此,它不知道如何实现这些功能,因此无法积极地进行优化.但我认为你现在还不需要关心它(:
仅包含标头的基本思想和仅编译cpp文件.一旦你有很多cpp文件,这将变得更有用,并且当你只修改其中一个文件时重新编译整个应用程序将会太慢.或者当文件中的函数相互依赖时.因此,您应该将类声明分离到头文件中,将实现保留在cpp文件中并编写Makefile(或其他东西,具体取决于您使用的工具)来编译cpp文件并将生成的目标文件链接到程序中.