Может кто-то помогать разъяснить, как заголовочные файлы работают?

Я работал с C++ для хороших нескольких недели теперь, но механизм позади заголовочных файлов (или компоновщик я предполагаю?) путает heck из меня. Я привык создавать "main.h", чтобы сгруппировать мои другие заголовочные файлы и сохранить main.cpp опрятным, но иногда те заголовочные файлы жалуются на неспособность найти другой заголовочный файл (даже при том, что это объявляется в "main.h"). Я, вероятно, не объясняю это очень хорошо, таким образом, вот сокращенная версия того, что я пытаюсь сделать:

//main.cpp

#include "main.h"
int main() {
    return 0;
}

-

//main.h

#include "player.h"
#include "health.h"
#include "custvector.h"

-

//player.h

#include "main.h"
class Player {
    private:
        Vector playerPos;
    public:
        Health playerHealth;
};

-

//custvector.h

struct Vector {
   int X;
   int Y;
   int Z;
};

-

//health.h
class Health {
    private:
        int curHealth;
        int maxHealth;
    public:
        int getHealth() const;
        void setHealth(int inH);
        void modHealth(int inHM);
};

Я не буду включать health.cpp, потому что это немного длинно (но действительно работает), это действительно имеет #include "health.h".

Так или иначе, компилятор (Код:: Блоки), жалуется, что "player.h" не может найти типы 'здоровьем' или 'Вектором'. Я думал это, если я использовал #include "main.h" в "player.h" это смогло бы найти определения для Health и Vector смысл они включены в "main.h". Я полагал, что они будут они сортировать туннеля свой путь хотя (player.h-> main.h-> health.h). Но это не работало слишком хорошо. Есть ли некоторая схема или видео, которое могло разъяснить, как это должно быть настроено? Google не был большим количеством справки (ни моя книга).

6
задан BJ Myers 19 May 2015 в 23:24
поделиться

5 ответов

Другие ответы здесь эффективно объяснили, как работают файлы заголовков и препроцессор. Самая большая проблема, с которой вы сталкиваетесь, - это циклические зависимости, которые, как я знаю, могут быть настоящей головной болью. Кроме того, когда это начинает происходить, компилятор начинает вести себя очень странно и выдавать сообщения об ошибках, которые не очень полезны. Метод, которому меня научил гуру C ++ в колледже, заключался в том, чтобы запускать каждый файл (например, файл заголовка) с

//very beginning of the file
#ifndef HEADER_FILE_H //use a name that is unique though!!
#define HEADER_FILE_H
...
//code goes here
...
#endif
//very end of the file

. При этом используются директивы препроцессора для автоматического предотвращения циклических зависимостей. Как правило, я всегда использую версию имени файла в верхнем регистре. custom-vector.h становится

#ifndef CUSTOM_VECTOR_H
#define CUSTOM_VECTOR_H

Это позволяет вам включать файлы willie-nillie без создания циклических зависимостей, потому что если файл включается несколько раз, его переменная препроцессора уже определена, поэтому препроцессор пропускает файл . Это также упрощает дальнейшую работу с кодом, потому что вам не нужно просматривать старые файлы заголовков, чтобы убедиться, что вы еще что-то не добавили. Я повторю еще раз, убедитесь, что имена переменных, которые вы используете в своих операторах #define , уникальны для вас, иначе вы можете столкнуться с проблемами, когда что-то не включается должным образом ;-).

Удачи!

8
ответ дан 8 December 2019 в 04:51
поделиться

Лучше всего думать о ваших файлах заголовков как о «автоматическом копировании и вставке».

Хороший способ подумать об этом (хотя и не о том, как это на самом деле реализовано) состоит в том, что, когда вы компилируете файл C или файл C ++, препроцессор запускается первым. Каждый раз, когда он встречает оператор #include, он фактически вставляет содержимое этого файла вместо оператора #include. Это делается до тех пор, пока не кончатся включения. Последний буфер передается компилятору.

Это вводит несколько сложностей:

Во-первых, если A.H включает B.H, а B.H включает A.h, у вас проблема. Потому что каждый раз, когда вы хотите вставить A, вам понадобится B, и у него внутри будет A! Это рекурсия. По этой причине в файлах заголовков используется #ifndef, чтобы гарантировать, что одна и та же часть не будет прочитана несколько раз.Скорее всего, это происходит в вашем коде.

Во-вторых, ваш компилятор C читает файл после того, как все файлы заголовков были «сглажены», поэтому вы должны учитывать это, когда рассуждаете о том, что объявлено перед тем.

10
ответ дан 8 December 2019 в 04:51
поделиться

включает в себя работу очень просто, они просто командуют препроцессору для добавления содержимого файла в то место, где установлено включение. основная идея - включить заголовки, от которых вы зависите. в player.h вы должны включить custvector.h и Health.h . В основном только player.h , потому что все необходимые включения будут перенесены с player. и вам вообще не нужно включать main.h в player.h .

Хорошо также убедиться, что заголовок включен только один раз. в этом вопросе дается общее решение Как предотвратить множественные определения в C? в случае Visual Studio вы можете использовать #pragma once , если Borland c ++ существует также уловка, но я забыл Это.

2
ответ дан 8 December 2019 в 04:51
поделиться

Вы хотите организовать свой #includes (и библиотеки, если на то пошло) в DAG (направленный, ациклический граф). Это сложный способ сказать «избегайте циклов между файлами заголовков»:

Если B включает A, A не должен включать B.

Итак, используя «one big master main.h "- неправильный подход, потому что сложно # включать только прямые зависимости.

Каждый файл .cpp должен включать свой собственный файл .h. Этот файл .h должен включать только то, что ему самому требуется для компиляции .

Обычно нет main.h , потому что main.cpp никому не нужно определение main.

Кроме того, вы захотите включить охранников , чтобы защитить вас от нескольких включений.

Например

//player.h
#ifndef PLAYER_H_
#define PLAYER_H_
#include "vector.h"  // Because we use Vector
#include "health.h"  // Because we use Health
class Player {
    private:
        Vector playerPos;
    public:
        Health playerHealth;
};
#endif

-

//vector.h
#ifndef VECTOR_H_
#define VECTOR_H_
struct Vector {
   int X;
   int Y;
   int Z;
};
#endif

-

//health.h
#ifndef HEALTH_H_
#define HEALTH_H_
class Health {
    private:
        int curHealth;
        int maxHealth;
    public:
        int getHealth() const;
        void setHealth(int inH);
        void modHealth(int inHM);
};
#endif

Единственный раз, когда вы хотите объединить группу #include в один заголовок, это когда вы предоставляете его как удобство для очень большой библиотеки.

В вашем текущем примере вы немного переборщили - каждому классу не нужен собственный файл заголовка. Он может все войдите в main.cpp.

Препроцессор c буквально вставляет файл из #include в файл, который включает его (если он еще не был вставлен, поэтому вам нужен include gu ОРДС). Это позволяет вам использовать классы, определенные в этих файлах, потому что теперь у вас есть доступ к их определению.

1
ответ дан 8 December 2019 в 04:51
поделиться

У вас круговая зависимость. Player включает main.h, а main.h включает player.h. Решите эту проблему, удалив одну зависимость или другую.\

Player.h должен включать health.h и custvector.h, и на данный момент, я не думаю, что main.h нуждается в каких-либо включениях. В конце концов, ему может понадобиться player.h.

3
ответ дан 8 December 2019 в 04:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: