每个 .C 或 .cpp 文件都应该有一个头 (.h) 文件吗?
假设有以下 C 文件:
主.C
功能1.C
函数2.C
函数3.C
在main()
Main.C 文件中的位置。应该有四个头文件
主文件
函数1.h
函数2.h
函数3.h
或者所有 .C 文件应该只有一个头文件?
什么是更好的方法?
每个 .C 或 .cpp 文件都应该有一个头 (.h) 文件吗?
假设有以下 C 文件:
主.C
功能1.C
函数2.C
函数3.C
在main()
Main.C 文件中的位置。应该有四个头文件
主文件
函数1.h
函数2.h
函数3.h
或者所有 .C 文件应该只有一个头文件?
什么是更好的方法?
首先,拥有 a 是不寻常的,main.h
因为在编译时通常不需要向其他编译单元公开任何内容。main()
函数本身需要为链接器或启动代码公开,但它们不使用头文件。
每个 C 文件可以有一个头文件,或者在我看来,更可能的是一组相关C 文件的头文件。
一个例子是,如果您有一个 BTree 实现,并且您已将添加、删除、搜索等放在它们自己的 C 文件中,以最大限度地减少代码更改时的重新编译。
在这种情况下,每个 C 文件都有单独的头文件是没有意义的,因为头文件是 API。换句话说,它是用户看到的图书馆的视图。使用您的代码的人通常很少关心您如何构建源代码,他们只是希望能够编写尽可能少的代码来使用它。
强迫他们包含多个不同的头文件,以便他们可以创建、插入、删除和搜索一棵树,这可能会让他们质疑你的理智:-)
最好使用一个 btree.h
文件和一个btree.lib
包含所有从单个 C 文件构建的 BTree 目标文件的文件。
另一个例子可以在标准 C 头文件中找到。
我们不确定是否所有stdio.h
函数都有多个 C 文件(我会这样做,但这不是唯一的方法),但即使有,它们也被视为一个单元API。
您不必包含stdio_printf.h
,stdio_fgets.h
等等 - stdio.h
C 运行时库的标准 I/O 部分有一个。
头文件不是强制性的。
#include
只需复制/粘贴包含的任何文件(包括 .c 源文件)
实际项目中常用的是全局头文件config.h
,constants.h
其中包含常用信息,例如编译时标志和项目范围的常量。
一个好的库 API 设计是使用一组头文件公开一个官方接口,并使用一组内部头文件来实现所有细节。这为 C 库添加了一个很好的额外抽象层,而不会增加不必要的膨胀。
使用常识。C/C++ 真的不适合没有它的人。
我曾经遵循“视情况而定”的趋势,直到我意识到一致性、统一性和简单性比节省创建文件的工作量更重要,并且“标准即使不好也是好的”。
我的意思是:一对 .cpp/.h 文件几乎是所有“模块”最终的结果。使这两个需求都存在可以避免很多混乱和糟糕的工程设计。
例如,当我在头文件中看到某些东西的接口时,我确切地知道在哪里搜索/放置它的实现。相反,如果我需要公开以前隐藏在 .cpp 文件中的某些东西的接口(例如,静态函数变成全局的),我知道该把它放在哪里。
我见过太多不遵守这条简单规则的不良后果。不必要的内联函数、违反任何关于封装的规则、接口和实现的(非)分离、错误放置的代码等等——这一切都是因为从未添加过适当的同级头文件或 cpp 文件。
所以:始终定义 .h 和 .c 文件。让它成为一个标准,遵循它,并安全地依赖它。这样生活就简单多了,简单是软件中最重要的事情。
通常,最好为每个 .c 文件创建一个头文件,其中包含要公开的 .c 文件中的函数等声明。这样,另一个 .c 文件可以包含 .h 文件以获得它需要的功能,并且如果它不包含的头文件发生更改,则不需要重新编译。
通常每个 .c/.cpp 文件都有一个 .h 文件。
这取决于。通常,您拥有单独的 .c 文件的原因将决定您是否需要单独的 .h 文件。
我喜欢将接口放入头文件并在 cpp 文件中实现。我不喜欢编写 C++,我需要将成员变量和原型添加到标头,然后在 C++ 中再次添加方法。我更喜欢这样的东西:
模块.h
struct IModuleInterface : public IUnknown
{
virtual void SomeMethod () = 0;
}
模块.cpp
class ModuleImpl : public IModuleInterface,
public CObject // a common object to do the reference
// counting stuff for IUnknown (so we
// can stick this object in a smart
// pointer).
{
ModuleImpl () : m_MemberVariable (0)
{
}
int m_MemberVariable;
void SomeInternalMethod ()
{
// some internal code that doesn't need to be in the interface
}
void SomeMethod ()
{
// implementation for the method in the interface
}
// whatever else we need
};
我发现这是一种分离实现和接口的非常干净的方法。
Bjarne Stroustrup 在他的《C++ 编程语言》一书中很好地解释了它......
当程序很小并且其部分不打算单独使用时,物理分区的单头样式最有用。当使用命名空间时,程序的逻辑结构仍然可以在单个头文件中解释。
对于较大的程序,单个头文件方法在传统的基于文件的开发环境中是行不通的。对公共头文件的更改会强制重新编译整个程序,并且几个程序员对单个头文件的更新很容易出错。除非非常重视严重依赖命名空间和类的编程风格,否则逻辑结构会随着程序的增长而恶化。
另一种物理组织让每个逻辑模块都有自己的标题来定义它提供的设施。然后每个 .c 文件都有一个对应的 h。文件指定它提供什么(它的接口)。每个 .c 模块都包含自己的 .h 文件,通常还包含其他 .h 文件,这些文件指定了它需要其他模块提供的内容,以实现在其接口中公布的服务。这种物理组织对应于模块的逻辑组织。多头方法可以很容易地确定依赖关系。单头方法迫使我们查看任何模块使用的每个声明并确定其是否相关。一个简单的事实是,代码的维护总是使用不完整的信息并从本地角度进行。
没有更好的方法,只有常见和不太常见的情况。
更常见的情况是当你有一个类/函数接口来声明/定义时。最好只有一个带有定义的 .cpp/.c 和一个用于声明的标头。给它们起相同的名字可以很容易地理解它们是直接相关的。
但这不是“规则”,这是几乎所有情况下最常见且最有效的方式。
现在在某些情况下(比如模板类或一些微小的结构定义)你不需要任何 .c/.cpp 文件,只需要标题。例如,我们经常只在头文件中定义一些虚拟类接口,只有虚拟纯函数或普通函数。
而在其他极少数情况下(如假设的 main.c/.cpp 文件),并不总是需要允许来自外部编译单元的代码调用给定编译单元的函数。main 函数是一个示例(不需要标头/声明),但还有其他的,主要是当它是“将所有其他部分连接在一起”并且不被应用程序的其他部分调用的代码时。这是非常罕见的,但在这种情况下,标题没有意义。
这完全是关于哪些代码需要知道哪些其他代码。您希望将其他文件知道的数量减少到最低限度,以便他们完成工作。
他们需要知道一个函数是否存在,他们需要传递什么类型,以及它将返回什么类型,但不知道它在内部做什么。请注意,从程序员的角度来看,了解这些类型的实际含义也很重要。(例如,哪个 int 是行,哪个是列)但代码本身并不关心。这就是为什么明智地命名函数和参数是值得的。
正如其他人所说,如果 cpp 文件中没有任何内容值得暴露给代码的其他部分,就像 main.c 一样,那么就不需要头文件。
有时值得将您想要公开的所有内容放在一个头文件中(例如,Func1and2and3.h),以便了解 Func1 的任何人也了解 Func2,但我个人并不热衷于此,因为这意味着您往往会加载大量垃圾以及您真正想要的东西。
总结: 想象一下,你相信有人会写代码,并且他们的算法、设计等都很好。您想使用他们编写的代码。所有你需要知道的是给他们什么来让事情发生,你应该给什么,你会得到什么。这就是头文件中需要包含的内容。
如果您的文件公开了一个接口——也就是说,如果它具有将从其他文件调用的函数——那么它应该有一个头文件。否则,它不应该。
如前所述,通常每个源文件(.c 或 .cpp)都有一个头文件 (.h)。
但是,您应该查看文件的内聚性。如果各种源文件提供单独的、可单独重用的功能集——一个理想的组织——那么每个文件当然应该有一个头文件。但是,如果这三个源文件提供了一组复合函数(太大而无法放入一个文件中),那么您将使用更复杂的组织结构。主程序使用的外部服务将有一个标头 - 其他需要相同服务的程序将使用该标头。合作的源文件还将使用第二个标头,该标头提供这些文件共享的“内部”定义。
(Pax 也指出):主程序通常不需要自己的头文件——其他源代码不应使用它提供的服务;它使用其他文件提供的服务。
如果您希望从另一个编译单元使用您的编译代码,您将需要头文件。在某些情况下,您现在确实需要/想要拥有标题。
第一种情况是 main.c/cpp 文件。这个类并不意味着包含在内,因此不需要头文件。
在某些情况下,您可以拥有一个头文件,该文件定义一组不同实现的行为,这些实现通过在运行时加载的 dll 加载。将有一组不同的 .c/.cpp 文件实现相同标头的变体。这在插件系统中很常见。
通常 cpp/c 文件用于实现,而 h/hpp(hpp 不经常使用)文件用于头文件(仅原型和声明)。Cpp 文件并不总是必须有一个与之关联的头文件,但它通常会这样做,因为头文件充当 cpp 文件之间的桥梁,因此每个 cpp 文件都可以使用来自另一个 cpp 文件的代码。
应该强烈执行的一件事是在头文件中不使用代码!由于重新定义,头文件在任何大小的项目中都会中断编译的次数太多了。这只是当您将头文件包含在 2 个不同的 cpp 文件中时。头文件也应始终设计为包含多次。永远不应包含 Cpp 文件。
一般来说,我认为 .h 和 .c 文件之间没有任何明确的关系。在许多情况下(可能是大多数情况下),一个代码单元是具有公共接口 (.h) 和不透明实现 (.c) 的功能库。有时需要一些符号,比如枚举或宏,而你得到一个没有对应 .c 的 .h,在少数情况下,你会得到一堆没有公共接口和对应 .h 的代码
特别是,有很多时候,为了可读性,头文件或实现(很少两者)太大而多毛,以至于为了程序员的理智,它们最终被分成许多较小的文件。