Я работал с 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 не был большим количеством справки (ни моя книга).
Другие ответы здесь эффективно объяснили, как работают файлы заголовков и препроцессор. Самая большая проблема, с которой вы сталкиваетесь, - это циклические зависимости, которые, как я знаю, могут быть настоящей головной болью. Кроме того, когда это начинает происходить, компилятор начинает вести себя очень странно и выдавать сообщения об ошибках, которые не очень полезны. Метод, которому меня научил гуру 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
, уникальны для вас, иначе вы можете столкнуться с проблемами, когда что-то не включается должным образом ;-).
Удачи!
Лучше всего думать о ваших файлах заголовков как о «автоматическом копировании и вставке».
Хороший способ подумать об этом (хотя и не о том, как это на самом деле реализовано) состоит в том, что, когда вы компилируете файл C или файл C ++, препроцессор запускается первым. Каждый раз, когда он встречает оператор #include, он фактически вставляет содержимое этого файла вместо оператора #include. Это делается до тех пор, пока не кончатся включения. Последний буфер передается компилятору.
Это вводит несколько сложностей:
Во-первых, если A.H включает B.H, а B.H включает A.h, у вас проблема. Потому что каждый раз, когда вы хотите вставить A, вам понадобится B, и у него внутри будет A! Это рекурсия. По этой причине в файлах заголовков используется #ifndef, чтобы гарантировать, что одна и та же часть не будет прочитана несколько раз.Скорее всего, это происходит в вашем коде.
Во-вторых, ваш компилятор C читает файл после того, как все файлы заголовков были «сглажены», поэтому вы должны учитывать это, когда рассуждаете о том, что объявлено перед тем.
включает в себя работу очень просто, они просто командуют препроцессору для добавления содержимого файла в то место, где установлено включение. основная идея - включить заголовки, от которых вы зависите. в player.h
вы должны включить custvector.h
и Health.h
. В основном только player.h
, потому что все необходимые включения будут перенесены с player. и вам вообще не нужно включать main.h
в player.h
.
Хорошо также убедиться, что заголовок включен только один раз. в этом вопросе дается общее решение Как предотвратить множественные определения в C? в случае Visual Studio вы можете использовать #pragma once
, если Borland c ++ существует также уловка, но я забыл Это.
Вы хотите организовать свой #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 ОРДС). Это позволяет вам использовать классы, определенные в этих файлах, потому что теперь у вас есть доступ к их определению.
У вас круговая зависимость. Player включает main.h, а main.h включает player.h. Решите эту проблему, удалив одну зависимость или другую.\
Player.h должен включать health.h и custvector.h, и на данный момент, я не думаю, что main.h нуждается в каких-либо включениях. В конце концов, ему может понадобиться player.h.