我习惯于在一个 C 文件中完成所有编码。然而,我正在从事一个足够大的项目,这样做是不切实际的。我一直将它们#include在一起,但我也遇到过多次#include某些文件的情况,等等。我听说过 .h 文件,但我不确定它们的功能是什么(或者为什么有 2 个文件比 1 个文件更好)。

我应该使用什么策略来组织我的代码?是否可以将特定文件的“公共”功能与“私有”功能分开?

这个问题引发了我的询问。tea.h 文件不引用 tea.c 文件。编译器是否“知道”每个 .h 文件都有对应的 .c 文件?

有帮助吗?

解决方案

您应该将 .h 文件视为 接口文件 你的 .c 文件。每个 .c 文件代表一个具有一定功能的模块。如果 .c 文件中的函数被其他模块使用(即其他.c文件)将函数原型放在.h接口文件中。通过将接口文件包含在您的原始模块 .c 文件以及您需要该函数的每个其他 .c 文件中,您可以使该函数可供其他模块使用。

如果您只需要某个 .c 文件中的函数(而不是任何其他模块中的函数),请将其范围声明为静态。这意味着它只能从定义它的 c 文件中调用。

对于跨多个模块使用的变量也是如此。它们应该放在头文件中,并且必须用关键字“extern”进行标记。笔记:对于函数,关键字“extern”是可选的。函数始终被视为“外部”。

头文件中的包含防护有助于避免多次包含相同的头文件。

例如:

模块1.c:

    #include "Module1.h"

    static void MyLocalFunction(void);
    static unsigned int MyLocalVariable;    
    unsigned int MyExternVariable;

    void MyExternFunction(void)
    {
        MyLocalVariable = 1u;       

        /* Do something */

        MyLocalFunction();
    }

    static void MyLocalFunction(void)
    {
      /* Do something */

      MyExternVariable = 2u;
    }

模块1.h:

    #ifndef __MODULE1.H
    #define __MODULE1.H

    extern unsigned int MyExternVariable;

    void MyExternFunction(void);      

    #endif

模块2.c

    #include "Module.1.h"

    static void MyLocalFunction(void);

    static void MyLocalFunction(void)
    {
      MyExternVariable = 1u;
      MyExternFunction();
    }

其他提示

尝试使每个 .c 专注于特定的功能领域。使用相应的 .h 文件来声明这些函数。

每个 .h 文件的内容周围都应该有一个“标头”保护。例如:

#ifndef ACCOUNTS_H
#define ACCOUNTS_H
....
#endif

这样,您可以根据需要多次包含“accounts.h”,并且第一次在特定编译单元中看到它时将是唯一实际提取其内容的编译单元。

编译器

您可以在以下位置查看 C“模块”的示例 这个话题 - 请注意,有两个文件 - 头文件 tea.h 和代码 tea.c。您可以在标头中声明您希望其他程序访问的所有公共定义、变量和函数原型。在您的主项目中,您将 #include ,并且该代码现在可以访问标题中提到的 tea 模块的函数和变量。

之后事情会变得更复杂一些。如果您使用 Visual Studio 和许多其他为您管理构建的 IDE,请忽略这部分 - 它们负责编译和链接对象。

链接器

当您编译两个单独的 C 文件时,编译器会生成单独的目标文件 - 因此 main.c 变为 main.o,而 tea.c 变为 tea.o。链接器的工作是查看所有目标文件(您的 main.o 和 tea.o),并匹配引用 - 因此,当您在 main 中调用 tea 函数时,链接器会修改该调用,因此它实际上确实调用了正确的在茶中发挥作用。链接器生成可执行文件。

有一个 很棒的教程 这将更深入地讨论这个主题,包括范围和您将遇到的其他问题。

祝你好运!

-亚当

从几个简单的规则开始:

  1. 将您想要“公开”的那些声明放入您正在创建的 C 实现文件的头文件中。
  2. 仅在C文件中#include实现C文件所需的头文件。
  3. 仅当头文件中的声明需要时,才将头文件包含在头文件中。

  4. 使用 Andrew 描述的 include Guard 方法或使用 #pragma 一次 如果编译器支持它(它会做同样的事情——有时更有效)

回答您的附加问题:

这个问题引发了我的询问。Tea.h文件没有引用Tea.c文件。编译器是否“知道”每个.h文件都有一个相应的.c文件?

编译器主要不关心头文件。编译器的每次调用都会将源 (.c) 文件编译为对象 (.o) 文件。在幕后(即在里面 make 文件或项目文件)正在生成与此等效的命令行:

compiler --options tea.c

源文件 #includes 它引用的资源的所有头文件,这就是编译器查找头文件的方式。

(我在这里掩盖了一些细节。关于构建 C 项目有很多东西需要学习。)

除了上面提供的答案之外,将代码分成模块(单独的文件)的一个小优点是,如果您必须有任何全局变量,您可以通过使用关键字 ' 将它们的范围限制为单个模块静止的'。(您也可以将其应用于函数)。请注意,“静态”的这种使用与其在函数内的使用不同。

你的问题清楚地表明你还没有真正进行过认真的开发。通常的情况是,您的代码通常太大而无法放入一个文件中。一个好的规则是,您应该将功能拆分为逻辑单元(.c 文件),并且每个文件所包含的内容不应超过您一次可以轻松记住的内容。

给定的软件产品通常包含许多不同 .c 文件的输出。通常情况下,编译器会生成许多目标文件(在unix系统中为“.o”文件,VC生成.obj文件)。“链接器”的目的是将这些目标文件组合成输出(共享库或可执行文件)。

通常,您的实现 (.c) 文件包含实际的可执行代码,而头文件 (.h) 则在这些实现文件中包含公共函数的声明。您可以很容易地拥有比实现文件更多的头文件,有时头文件也可以包含内联代码。

实现文件相互包含通常是很不常见的。一个好的做法是确保每个实现文件将其关注点与其他文件分开。

我建议您下载并查看 Linux 内核的源代码。对于 C 程序来说,它相当庞大,但很好地组织成不同的功能区域。

.h 文件应用于定义函数的原型。这是必要的,这样您就可以在 C 文件中包含所需的原型,而无需在一个文件中声明所需的每个函数。

例如,当您 #include <stdio.h>, ,这提供了 printf 和其他 IO 函数的原型。这些函数的符号通常由编译器默认加载。如果您对与这些文件相关的常规习惯感兴趣,可以查看 /usr/include 下的系统 .h 文件。

如果您只是编写功能不多的简单应用程序,则实际上没有必要将所有内容模块化为过程的逻辑分组。但是,如果您需要开发大型系统,那么您需要考虑在哪里定义每个函数。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top