2010-06-23 7 views
6

Ho lavorato con C++ per un paio di settimane, ma il meccanismo dietro i file di intestazione (o il linker suppongo?) Mi confonde. Ho preso l'abitudine di creare un "main.h" per raggruppare i miei altri file header e mantenere l'ordinamento main.cpp, ma a volte quei file header lamentano di non essere in grado di trovare un file header differente (anche se è dichiarato nel "main.h"). Non sto probabilmente spiegare molto bene quindi ecco una versione ridotta di quello che sto cercando di fare:Qualcuno può aiutare a chiarire come funzionano i file di intestazione?

//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); 
}; 

Non includerò health.cpp perché è un po 'lungo (ma funziona), ha #include "health.h".

In ogni caso, il compilatore (Code :: Blocks) lamenta che "player.h" non riesce a trovare i tipi "Salute" o "Vettore". Ho pensato che se avessi usato #include "main.h" in "player.h", sarebbe stato in grado di trovare le definizioni per Health e Vector in modo che fossero incluse in "main.h". Ho pensato che avrebbero fatto una sorta di tunnel a modo loro però (player.h -> main.h -> health.h). Ma quello non ha funzionato troppo bene. C'è qualche tipo di diagramma o video che potrebbe chiarire come dovrebbe essere impostato? Google non è stato di grande aiuto (né il mio libro).

+1

Non risponde alla domanda, ma è necessario modificare la struttura del vettore con un nome diverso. Crescerà confuso quando inizierai a usare std :: vector, e un punto nello spazio 3D non è comunque un vettore. – reuscam

+0

Grazie, lo farò. E hai ragione, dovrebbe essere Point o qualcosa del genere. –

+0

In realtà, un vettore è definito da un solo punto. – Spidey

risposta

8

Le altre risposte qui hanno effettivamente spiegato il modo in cui funzionano i file di intestazione e il preprocessore. Il più grande problema che hai sono le dipendenze circolari, che per esperienza, so che può essere un dolore reale. Inoltre, quando inizia a verificarsi, il compilatore inizia a comportarsi in modi molto strani e lancia messaggi di errore che non sono molto utili. Il metodo mi è stato insegnato da un C++ guru al college è stato quello di iniziare ogni file (un file di intestazione per esempio) con

//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 

Questo utilizza direttive del preprocessore per evitare automaticamente le dipendenze circolari. Fondamentalmente, io uso sempre una versione in maiuscolo del nome del file. custom-vector.h diventa

#ifndef CUSTOM_VECTOR_H 
#define CUSTOM_VECTOR_H 

Ciò consente di includere i file Willie-Nillie senza creare dipendenze circolari, perché se un file viene incluso più volte, la sua variabile preprocessore è già definita, in modo che il preprocessore salta il file. Rende inoltre più facile lavorare in seguito con il codice perché non devi setacciare i vecchi file di intestazione per assicurarti di non aver già incluso qualcosa. Ripeterò comunque, assicurati che i nomi delle variabili che usi nelle tue istruzioni #define siano unici per te altrimenti potresti incorrere in problemi in cui qualcosa non viene incluso correttamente ;-).

Buona fortuna!

+0

Spero non ti dispiaccia: ho modificato il tuo esempio CUSTOM_VECTOR_CPP . I file '* .c' e' * .cpp' non hanno bisogno di includere guardie poiché non li si include. –

+0

Quindi, ad esempio, se dovessi creare una classe di nemici che necessitasse di Vector e Salute, e la classe Player includesse già Vector e Salute, non avrei bisogno di includerli per il nemico? –

+0

@John Nessun problema!Buona call, sono abituato alla programmazione di template in cui devi includere i file * .cpp nei file * .h. Ho appena preso l'abitudine di farlo :-) –

3

Si dispone di una dipendenza circolare. Il giocatore include main.h, ma main.h include player.h. Risolvi questo rimuovendo una dipendenza o l'altra. \

Player.h dovrebbe includere health.h e custvector.h e, a questo punto, non penso che main.h abbia bisogno di alcun include. Alla fine potrebbe essere necessario player.h.

+1

#ifndef ... #endif è un altro, un modo leggermente più semplice per andare pure –

+0

Non sarà un problema se qualcos'altro deve usare health.h? Non lo includerebbe nel programma due volte? –

+1

Il modo per evitare di includere un file di intestazione più volte in C o C++ consiste nell'utilizzare un include guard. Ogni file di intestazione inizia con le due linee '#ifndef UNIQUE_STRING' e' #define UNIQUE_STRING' (sostituendo UNIQUE_STRING con un nome che non si '# define' altrove). Il file termina con '# endif'. http://en.wikipedia.org/wiki/Include_guard –

2

include un lavoro molto semplice, basta che comandi il preprocessore per aggiungere il contenuto del file a dove è impostato l'inclusione. l'idea di base è includere le intestazioni da cui dipende. in player.h dovresti includere custvector.h e Health.h. In main solo player.h, perché tutti gli include necessari saranno trasportati con il giocatore. e non è necessario includere main.h in player.h.

è bene anche assicurarsi che l'intestazione sia inclusa solo una volta. in questa domanda la soluzione generale è data How to prevent multiple definitions in C? nel caso di Visual Studio è possibile utilizzare #pragma once, se Borland C++ c'è anche un trucco ma l'ho dimenticato.

+0

Immagino sia quello di cui ero preoccupato, non volevo aggiungerlo due volte se decidessi di creare qualcosa come una classe nemica che necessitasse anche di Vector and Health. Ho pensato che se avessi inserito tutte le definizioni in main.h, eliminerebbe quel problema. –

+0

@Karl Menke 'main.h' non è un buon posto per metterlo, perché conterrà anche cose per main.cpp. dovresti creare qualcosa come 'common.h' e spostare i riferimenti comuni lì. quindi usarlo. – Andrey

10

Il modo migliore per pensare ai file di intestazione è come un "copia-e-incolla automatizzato".

Un buon modo di pensarci (anche se non come questo è effettivamente implementato) è che quando si compila un file C o un file C++, il preprocessore viene eseguito per primo. Ogni volta che incontra un'istruzione #include, in realtà incolla il contenuto di quel file invece dell'istruzione #include. Questo è fatto fino a quando non ci sono più include. Il buffer finale viene passato al compilatore.

Questo introduce diverse complessità:

In primo luogo, se A.H include B.H e B.H comprende A.h, hai un problema. Perché ogni volta che si desidera incollare A, è necessario B e internamente A! Questa è una ricorsione. Per questo motivo, i file di intestazione utilizzano #ifndef, per garantire che la stessa parte non venga letta più volte. Questo probabilmente sta succedendo nel tuo codice.

In secondo luogo, il compilatore C legge il file dopo che tutti i file di intestazione sono stati "appiattiti", quindi è necessario considerarlo quando si ragiona su cosa viene dichiarato prima di cosa.

+0

Questa è la spiegazione che mi piace di più, e quella che descrivo meglio come ho capito questo problema. È semplice, il compilatore prende il testo e lo compila. Il preprocessore prende il testo e lo pre-processa. Include solo una direttiva per il preprocessore per importare del testo da un file esterno nel file corrente. – Spidey

+0

Grazie, questo aiuta. –

+0

+1 Semplice e bello .. :) – liaK

1

C'è qualche tipo di diagramma o video che potrebbe chiarire come dovrebbe essere impostato?

Prova la mia risposta alla domanda "Clean up your #include statements?".

+0

Grazie, guarderò quello. –

+0

@Karl Menke - C'è un altro [qui] (http://stackoverflow.com/questions/1598207/odd-circular-dependency-issue/1598257#1598257) – ChrisW

1

Si desidera organizzare il proprio #includes (e le relative librerie) in un DAG (grafico diretto, aciclico). Questo è il modo complicato di dire "evitare cicli tra i file di intestazione":

Se B comprende una, A non dovrebbe includere B.

Quindi, l'utilizzo di "un grande maestro main.h" non è l'approccio giusto, perché è difficile includere solo le dipendenze dirette.

Ogni file .cpp deve includere il proprio file .h. Quel file .h dovrebbe includere solo le cose che esso stesso richiede per compilare.

Di solito non esiste il numero main.h, perché nessuno ha bisogno della definizione di main perché main.cpp.

Inoltre, è necessario lo include guards per proteggersi da più inclusioni.

Per esempio

//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 

L'unica volta che si vuole aggregare un gruppo di #include s in un unico colpo di testa è quando sei fornendolo come comodità per una libreria molto grande.

Nell'esempio corrente, stai andando un po 'fuori bordo - ogni classe non ha bisogno del proprio file di intestazione. È possibile tutti andare in main.cpp.

Il preprocessore c inserisce letteralmente il file da un #include nel file che lo include (a meno che non sia già stato inserito, motivo per cui è necessario includere le protezioni). Ti permette di usare le classi definite in quei file perché ora hai accesso alla loro definizione.

Problemi correlati