为什么C ++具有头文件和.cpp文件?
为什么C ++具有头文件和.cpp文件?
Answers:
好吧,主要原因是要从实现中分离接口。标头声明一个类(或正在实现的任何东西)将做什么,而cpp文件定义了它将如何执行那些功能。
这减少了依赖性,因此使用标头的代码不一定需要了解实现的所有细节以及仅为此所需的任何其他类/标头。当实现中的某些内容发生更改时,这将减少编译时间,并减少所需的重新编译量。
它不是完美的,通常您会使用Pimpl Idiom之类的技术来正确分离接口和实现,但这是一个好的开始。
C ++的编译分为两个主要阶段:
第一个是将“源”文本文件编译为二进制“对象”文件:CPP文件是已编译文件,并且在不了解其他CPP文件(甚至库)的情况下进行编译,除非通过原始声明或标头包含。CPP文件通常被编译为.OBJ或.O“对象”文件。
第二个是将所有“对象”文件链接在一起,从而创建最终的二进制文件(库或可执行文件)。
HPP在所有这些过程中适合什么位置?
每个CPP文件的编译都独立于所有其他CPP文件,这意味着如果A.CPP需要B.CPP中定义的符号,例如:
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
它不会编译,因为A.CPP无法知道“ doSomethingElse”的存在...除非A.CPP中有声明,例如:
// A.CPP
void doSomethingElse() ; // From B.CPP
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
然后,如果您有使用相同符号的C.CPP,则可以复制/粘贴声明...
是的,有问题。复制/粘贴很危险,并且难以维护。这意味着如果我们有某种方法可以不复制/粘贴并仍然声明符号,那将很酷……我们该怎么做?通过包含一些文本文件,通常以.h,.hxx,.h ++为后缀,或者,我偏爱C ++文件的.hpp:
// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;
// A.CPP
#include "B.HPP"
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
// B.CPP
#include "B.HPP"
void doSomethingElse()
{
// Etc.
}
// C.CPP
#include "B.HPP"
void doSomethingAgain()
{
doSomethingElse() ; // Defined in B.CPP
}
include
工作如何?从本质上讲,包括一个文件将解析然后将其内容粘贴到CPP文件中。
例如,在以下代码中,带有A.HPP标头:
// A.HPP
void someFunction();
void someOtherFunction();
...来源B.CPP:
// B.CPP
#include "A.HPP"
void doSomething()
{
// Etc.
}
...包含后将变为:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
在当前情况下,这是不需要的,并且B.HPP具有doSomethingElse
函数声明,而B.CPP具有doSomethingElse
函数定义(本身就是声明)。但是在更一般的情况下,如果B.HPP用于声明(和内联代码),则可能没有相应的定义(例如,枚举,普通结构等),因此,如果B.CPP需要使用include。使用来自B.HPP的那些声明。总而言之,默认情况下,包含源头是“好习惯”。
因此,头文件是必需的,因为C ++编译器无法单独搜索符号声明,因此,您必须通过包括这些声明来提供帮助。
最后一句话:您应该在HPP文件的内容周围放置标题防护,以确保多个包含内容不会破坏任何内容,但是总而言之,我相信上面已经解释了存在HPP文件的主要原因。
#ifndef B_HPP_
#define B_HPP_
// The declarations in the B.hpp file
#endif // B_HPP_
甚至更简单
#pragma once
// The declarations in the B.hpp file
You still have to copy paste the signature from header file to cpp file, don't you?
不需要。只要CPP“包括” HPP,预编译器就会自动将HPP文件的内容复制粘贴到CPP文件中。我更新了答案以澄清这一点。
While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
。不,不是。它只知道用户提供的类型,这将有一半的时间甚至不会花时间读取返回值。然后,发生隐式转换。然后,当您拥有代码:时foo(bar)
,您甚至无法确定它foo
是一个函数。因此,编译器必须有权访问头文件中的信息,以决定源代码是否正确编译。然后,一旦编译了代码,链接器将链接函数调用。
Seems, they're just a pretty ugly arbitrary design.
:如果确实在2012年创建了C ++。但是请记住,C ++是在1980年代建立在C之上的,当时的约束条件是完全不同的(IIRC,出于采用目的,决定保持与C相同的链接器)。
foo(bar)
是一个函数-如果它是作为指针获得的呢?实际上,谈到糟糕的设计,我指责C,而不是C ++。我真的不喜欢纯C的一些约束,例如具有头文件或使函数返回一个且只有一个值,同时在输入上采用多个参数(让输入和输出以相似的方式运行是不自然的, ;为什么
Why can't I be sure, that foo(bar) is a function
foo可能是一种类型,因此您将有一个称为的类构造函数。In fact, speaking of bad design, I blame C, not C++
:我可以在很多事情上归咎于C,但是在70年代进行设计不会是其中之一。再次,那个时间的约束... such as having header files or having functions return one and only one value
:元组可以帮助减轻这种情况,以及通过引用传递参数。现在,检索返回的多个值的语法是什么,更改语言是否值得?
因为C的概念起源于30年,而那时C是将多个文件中的代码链接在一起的唯一可行方法。
如今,这是一个可怕的骇客,它完全破坏了C ++中的编译时间,导致无数不必要的依赖(因为头文件中的类定义暴露了太多有关实现的信息),等等。
因为C ++从C继承了它们。不幸的是。
因为设计库格式的人不希望“浪费”一些很少使用的信息,例如C预处理器宏和函数声明。
由于您需要该信息来告诉编译器“链接器完成其工作后,此功能才可用”,因此他们不得不拿出另一个文件来存储此共享信息。
C / C ++之后的大多数语言都将此信息存储在输出中(例如Java字节码),或者根本不使用预编译格式,总是以源代码形式分发并即时编译(Python,Perl)。
通常,您将需要定义接口而不必交付整个代码。例如,如果您有一个共享库,则将附带一个头文件,该头文件定义了共享库中使用的所有功能和符号。如果没有头文件,则需要发送源代码。
在单个项目中,至少将IMHO头文件用于两个目的:
回应MadKeithV的回答,
这减少了依赖性,因此使用标头的代码不一定需要了解实现的所有细节以及仅为此所需的任何其他类/标头。这将减少编译时间,并且当实现中的某些内容发生更改时也将减少重新编译的次数。
另一个原因是,标头为每个类赋予唯一的ID。
所以如果我们有类似的东西
class A {..};
class B : public A {...};
class C {
include A.cpp;
include B.cpp;
.....
};
当我们尝试构建项目时,由于A是B的一部分,所以我们会遇到错误,并且使用标头可以避免这种麻烦……