【问题标题】:How to match .c and .h properly?如何正确匹配 .c 和 .h?
【发布时间】:2017-12-09 13:48:49
【问题描述】:

我有一个 C 项目,但我真的不知道如何管理 .c 和 .h。 我多年来一直在使用一种方法,但现在我必须在没有任何警告的情况下创建代码,所以我需要改变我的做法。

这是我的做法:

每个 .c 都有一个同名的 .h。甚至 main.c 都有一个 main.h。

在每个 .c 中,我都包含匹配的 .h,因此每个 .c 我只有一个包含。 在 main.h 中,我包含了我需要的所有库,例如 stdio stdlib 等。我还在那里声明了我的枚举和结构。

最后,在其他每个 .h 中,我都包含 main.h,其中包含结构、枚举和库。

我知道这样做,我会调用 main.h 很多时间,但我在每个 .h 中都使用 #ifndef#define。 此外,我每个 .h 都在其对应的 .c 中包含函数的原型

我应该如何管理 .c 和 .h?

我忘了提到问题是我对每个函数都收到警告:“函数的隐式声明”

这是一个包含四个文件的示例:

main.c:

#include "main.h"

int main() 
{
    // code
}
int save(Player players[2], int currentPlayer)
{
    // code
}
int load(Player players[2], int* currentPlayer)
{
    // code
}

main.h:

#ifndef MAIN_H
#define MAIN_H

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#include <string.h>
#include <math.h>

typedef enum Direction Direction;
typedef enum Orientation Orientation;
typedef enum ShipType ShipType;
typedef enum CaseType CaseType;
typedef enum Status Status;
typedef enum PlayerType PlayerType;

enum Direction
{
    Nord=0, Est=1, Sud=2, Ouest=3
};
enum Orientation
{
    None=0, Horizontal=1, Vertical=2
};
enum ShipType
{
    SousMarin=1, Destroyer=2, Croiseur=3, Cuirasse=4
};
enum CaseType
{
    Undiscovered=0, Empty=1, Hit=2, Detected=3, Discovered=5
};

enum Status
{
    Undamaged=0, Damaged=1, Drowned=2
};

enum PlayerType
{
    Human=0, Computer=1
};

typedef struct Map Map;
typedef struct Ship Ship;
typedef struct Player Player;

struct Map
{
    int width, height;
    int* cases;
};

struct Ship
{
    int x, y, length, firstShoot, color;
    int hasBeenDiscovered;
    Orientation orientation;
    ShipType type;
    Status status;
};

struct Player
{
    int totalShips;
    int activeShips;
    Map map[2];
    char lastMoves[5][128];
    Ship* ships;
    PlayerType type;
    int shipcolor[4];
    int shipNumber[4];
    int color;
};

int save(Player players[2], int currentPlayer);
int load(Player players[2], int* currentPlayer);

#endif // MAIN_H

IA.c:

#include "IA.h"

int IA(Player* IA, Player* enemy)
{
    // code
}

// And all my other functions

IA.h:

#ifndef IA_H
#define IA_H

#include "main.h"

int IA(Player* IA, Player* enemy);
void endIATurn(Player* IA);

int enemyShipDiscovered(Map map, int* x, int* y, int SousMarinSpecific);
Ship* bestFriendlyActiveShip(Ship* ships, int totalShips, int SousMarinSpecific);
int friendlyShipDiscovered(Ship** ship, Ship* ships, Map map, int totalShips, int SousMarinSpecific);
int hitShipButNotDrowned(Player* IA, Player enemy, int totalShips, int* x, int* y);

int hasAtLeastOneMoveableShip(Player player);
int hasUndiscoveredCases(Player player);

void getUndiscoveredCoordoonnees(Map map, int* x, int* y);
int findNextCaseToShootAt(Map map, Ship ship, int* a, int* b);

#endif // IA_H

我所有的其他 .h 都像 IA.h

【问题讨论】:

  • 考虑到您处理文件的方式,我更期待一个 duplicate 声明...我们可以看看.c 及其对应的.h 的摘录吗?
  • 而您已经收到这些警告 并且直到现在才考虑对此采取任何措施!?如果你有一个“隐式声明”,编译器在调用函数时看不到声明 - 不知何故你没有包含标题或标题不正确。从您的文字描述中,您在做什么还很不清楚;贴一些例子。
  • 问题可能是您在main 函数中使用了其他文件中的函数,而没有包括这些文件的标题。在main.cpp 中包含main.h 是不够的,您需要包含包含您使用的每个函数的标题(例如,如果您的main 函数使用来自foo.c 的函数,则需要包含@987654335 @ 在main.cmain.h)。同样,如果你想在foo.c中使用来自bar.c的函数,你需要在foo.c中包含bar.h
  • .h 文件应该只有 #include 用于它直接依赖的标头。 .c.cpp 文件应该包含它自己的头文件,以及它直接依赖的所有头文件。你的头文件应该有头文件保护和/或#pragma once,这取决于你的编译器。您放入main.h 的所有内容我都会放入common.h,只是为了更清楚。对于较大的项目,请考虑制作 header-headers。

标签: c project header-files


【解决方案1】:

对于foo.hfoo.c

  • 使用foo.h 告诉其他源文件foo.c 提供的内容。所以foo.h 将声明(a)在foo.c 中定义的函数和(b)从其他源文件调用的函数。它还将声明这些函数需要或其他源文件能够使用这些函数所需的任何类型或其他内容。

  • foo.h中,只使用#include包含foo.h本身需要的头文件。

  • foo.c 中,使用#include "foo.h"。这有两个目的:(1) 它声明foo.c 可能需要的东西,而不必在foo.c 中复制这些声明。 (2) 它确保foo.c 使用与其他源文件在包含foo.h 时看到的相同声明,因此foo.h 中的声明与foo.c 中的定义之间的任何冲突都会产生编译器警告或错误消息。

  • 如果foo.c 需要foo.h 的任何标头,请将它们包含在foo.c 中,而不是foo.h。同样,如果foo.c 具有它自己使用的任何函数,但不希望其他源文件使用,则仅在foo.c 中声明它们。将仅用于 foo.c 的内容保存在 foo.c 中。 foo.h 仅用于将内容导出到其他源文件。

如果有你的程序一般使用的任何类型或其他声明:

  • 将它们放在一个适当命名的文件中(例如,bar.h 用于涉及某些 bar 概念的类型)并包含它。很大程度上,您可以将bar.h 视为上述一对bar.hbar.c,但bar.c 为空。

一般来说,没有理由创建main.h。通常,main.c 使用其他源文件提供的东西,而不提供其他源使用的东西(除了 main 由 C 运行时启动代码调用)。

【讨论】:

    【解决方案2】:

    我建议如下:

    1. 按照您的做法,将您的函数划分为不同的 .c 文件。为每个 .c 创建一个 .h
    2. 在每个 .h 中插入其 include 保护
      1. 在包含保护中,首先包含您需要声明函数或定义结构的库的 .h,例如#include &lt;stdlib.h&gt; 如果您使用 size_t 参数。
      2. 在包含保护中,插入需要导出的结构、枚举等
      3. 在包含保护中,插入函数声明。
    3. 在每个 .c 中首先包含其对应的 .h,然后包含实现您的功能所需的其他库,然后放入定义。
    4. 不要写main.hcommon.h,除非你有非常好的理由(我知道这会被评论,但每个人都有它的风格)。

    main.c 中包含您需要的内容。你会比你想象的少得多。

    【讨论】:

    • 如果我理解正确,我必须在每个 .h 中插入我的结构和枚举(如果我在 .h 中使用它们)?我不能只声明一次吗?
    • 您的问题应该是“为什么会出现这些错误”而不是“我如何组织我的文件”——后者与您的错误无关,主要是基于意见。如果解决方案是文件组织;你会在答案中得到答案——不要通过要求特定的解决方案来抢占答案——而是邀请所有的解决方案。
    • @Drakalex:每个结构或枚举类型应该只在一个标题中定义。如果其他标头也需要该信息,则这些标头应包含定义结构或枚举的唯一标头。头后卫处理残余问题。这实现了敏捷开发中的两个概念:SPOT - 单点真理和 DRY - 不要重复自己。使每个标头自包含(它在编译时不首先包含任何其他标头)和幂等(包含的频率无关紧要;这就是标头保护的用途)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    • 1970-01-01
    • 2016-07-13
    • 2021-09-09
    • 1970-01-01
    • 2016-09-08
    相关资源
    最近更新 更多