2011-01-21 4 views
130

A: http://www.learncpp.com/cpp-tutorial/19-header-files/Que sont les déclarations directes en C++?

Ce qui suit est mentionné:

add.cpp:

int add(int x, int y) 
{ 
    return x + y; 
} 

main.cpp:

#include <iostream> 

int add(int x, int y); // forward declaration using function prototype 

int main() 
{ 
    using namespace std; 
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl; 
    return 0; 
} 

Nous avons utilisé une déclaration avant afin que la compilateur sait ce que "add" était lors de la compilation main.cpp. Comme mentionné précédemment, écrire des déclarations pour chaque fonction que vous voulez utiliser dans un autre fichier peut devenir fastidieux rapidement.

Pouvez-vous expliquer «forward déclaration» plus loin? Quel est le problème si nous l'utilisons dans la fonction main()?

+0

Une « déclaration avant » est vraiment juste une déclaration. Voir (la fin de) cette réponse: http://stackoverflow.com/questions/1410563/what-is-the-difference-between-a-definition-and-a-declaration/1410632#1410632 – sbi

Répondre

266

Pourquoi l'avant-est nécessaire de déclarer C++

Le compilateur veut vérifier que vous avez pas fait des fautes d'orthographe ou dépassé le nombre incorrect d'arguments à la fonction. Ainsi, il insiste sur le fait qu'il voit d'abord une déclaration de 'add' (ou d'autres types, classes ou fonctions) avant qu'il ne soit utilisé.

Cela permet vraiment au compilateur de mieux valider le code et de le ranger pour qu'il puisse produire un fichier objet soigné. Si vous n'aviez pas à transmettre des choses, le compilateur produirait un fichier objet qui devrait contenir des informations sur toutes les suppositions possibles quant à ce que pourrait être la fonction 'add'. Et l'éditeur de liens devrait contenir une logique très intelligente pour essayer de déterminer quel 'ajout' vous voulez réellement appeler, quand la fonction 'add' peut vivre dans un fichier objet différent que le linker rejoint avec celui qui utilise add pour produire un DLL ou un exe. Il est possible que l'éditeur de liens ait un mauvais ajout. Supposons que vous vouliez utiliser int add (int a, float b), mais que vous ayez accidentellement oublié de l'écrire, mais l'éditeur de liens a trouvé un int déjà existant (int a, int b) et a pensé que c'était le bon. Votre code compilerait, mais ne ferait pas ce que vous attendiez. Donc, juste pour garder les choses explicites et éviter les devinettes etc, le compilateur insiste pour que vous déclariez tout avant de l'utiliser.

différence entre la déclaration et la définition

En aparté, il est important de connaître la différence entre une déclaration et une définition. Une déclaration donne juste assez de code pour montrer à quoi ressemble quelque chose, donc pour une fonction, c'est le type de retour, la convention d'appel, le nom de la méthode, les arguments et leurs types. Mais le code de la méthode n'est pas requis. Pour une définition, vous avez besoin de la déclaration, puis aussi du code de la fonction.

Comment l'avant-déclarations peuvent considérablement réduire le temps de construction

Vous pouvez obtenir la déclaration d'une fonction dans votre .cpp ou le fichier .h par # includ'ing l'en-tête qui contient déjà une déclaration de la fonction. Cependant, cela peut ralentir votre compilation, surtout si vous #incluez un en-tête dans un .h au lieu de .cpp de votre programme, car tout ce qui #inclut le .h que vous écrivez finirait par # inclure tous les en-têtes. tu as écrit #includes aussi. Soudainement, le compilateur a inclus # les pages et les pages de code qu'il doit compiler même si vous vouliez seulement utiliser une ou deux fonctions. Pour éviter cela, vous pouvez utiliser une déclaration forward et taper vous-même la déclaration de la fonction en haut du fichier. Si vous n'utilisez que quelques fonctions, cela peut vraiment rendre vos compilations plus rapides par rapport à toujours #including l'en-tête. Pour les projets de très grande taille, la différence pourrait être d'une heure ou plus de temps de compilation réduit à quelques minutes.

références cycliques Break où deux définitions utilisent tous deux les uns des autres

En outre, les déclarations prévisionnelles peuvent vous aider à briser les cycles. C'est là que deux fonctions essayent de s'utiliser l'une l'autre. Quand cela arrive (et c'est une chose parfaitement valide à faire), vous pouvez #include un fichier d'en-tête, mais ce fichier d'en-tête essaie #include le fichier d'en-tête que vous écrivez actuellement .... qui #include l'autre en-tête , qui inclut # celui que vous écrivez. Vous êtes coincé dans une situation de poule et d'oeuf avec chaque fichier d'en-tête essayant de re # inclure l'autre. Pour résoudre ce problème, vous pouvez déclarer les pièces dont vous avez besoin dans l'un des fichiers et laisser le #include hors de ce fichier.

Par exemple:

Fichier Car.h

#include "Wheel.h" // Include Wheel's definition so it can be used in Car. 
#include <vector> 

class Car 
{ 
    std::vector<Wheel> wheels; 
}; 

Fichier Wheel.h

Hmm ... la déclaration de voiture est nécessaire ici que la roue a un pointeur vers un Car, mais Car.h ne peut pas être inclus ici car cela entraînerait une erreur de compilation. Si Car.h était inclus, cela essaierait alors d'inclure Wheel.h qui inclurait Car.h qui inclurait Wheel.h et cela continuerait pour toujours, ainsi le compilateur génère une erreur. La solution est de transmettre voiture au lieu de déclarer:

class Car;  // forward declaration 

class Wheel 
{ 
    Car* car; 
}; 

Si la roue de classe avait des méthodes qui ont besoin d'appeler des méthodes de voiture, ces méthodes pourraient être définies dans Wheel.cpp et Wheel.cpp est maintenant en mesure d'inclure Car.h sans provoquer de cycle.

+3

déclaration avant est également nécessaire quand une fonction est amicale à deux ou plusieurs classes – Barun

+0

Hey Scott, sur votre point sur les temps de construction: Diriez-vous qu'il est une pratique courante de toujours transmettre et inclure les en-têtes au besoin dans le fichier. ? À la lecture de votre réponse, il semblerait que ce devrait être le cas, mais je me demande s'il y a des réserves? – Zepee

+4

@Zepee C'est un équilibre. Pour des constructions rapides, je dirais que c'est une bonne pratique et je recommande de l'essayer. Cependant, cela peut demander un certain effort et des lignes de code supplémentaires peuvent devoir être maintenues et mises à jour si les noms de types etc. sont encore en train d'être changés (bien que les outils s'améliorent pour renommer les choses automatiquement). Il y a donc un compromis. J'ai vu des bases de code où personne ne dérange. Si vous vous retrouvez à répéter les mêmes définitions, vous pouvez toujours les mettre dans un fichier d'en-tête séparé et l'inclure, quelque chose comme: http://stackoverflow.com/questions/4300696/what-is-the-iosfwd-header –

10

Parce que C++ est analysé de haut en bas, le compilateur doit connaître les choses avant qu'ils ne soient utilisés. Ainsi, lorsque vous référencez:

int add(int x, int y) 

dans la fonction principale, le compilateur doit savoir qu'il existe. Pour le prouver, essayez de le placer en dessous de la fonction principale et vous obtiendrez une erreur de compilation.

Donc, un 'Forward Déclaration' est exactement ce qu'il dit sur l'étain. C'est déclarer quelque chose avant son utilisation.

En règle générale, vous devez inclure les déclarations directes dans un fichier d'en-tête, puis inclure ce fichier d'en-tête de la même manière que iostream est inclus.

0

Un problème est que le compilateur ne sait pas, quel type de valeur est délivré par votre fonction; est supposé, que la fonction renvoie un int dans ce cas, mais cela peut être aussi correct que cela peut être faux. Un autre problème est que le compilateur ne sait pas quel type d'arguments attend votre fonction et ne peut pas vous avertir si vous passez des valeurs du mauvais type. Il existe des règles spéciales de "promotion", qui s'appliquent lors du passage, dites des valeurs flottantes à une fonction non déclarée (le compilateur doit les élargir au type double), ce qui n'est souvent pas le cas, ce qui rend difficile la recherche de bogues. lors de l'exécution.

1

Lorsque le compilateur voit add(3, 4), il doit savoir ce que cela signifie. Avec la déclaration forward vous dites essentiellement au compilateur que add est une fonction qui prend deux ints et retourne un int. C'est une information importante pour le compilateur, car il doit mettre 4 et 5 dans la bonne représentation sur la pile et doit savoir de quel type la chose retournée par add est.

A cette époque, le compilateur est pas inquiet au sujet de la réelle mise en œuvre de add, à savoir où il est (ou si est même un) et si elle compile. Cela apparaît plus tard, après compiler les fichiers source lorsque l'éditeur de liens est appelé.

1
int add(int x, int y); // forward declaration using function prototype 

Pouvez-vous expliquer "déclaration avant" plus encore? Quel est le problème si nous l'utilisons dans la fonction principale()?

C'est la même chose que #include"add.h". Si vous le savez, le préprocesseur développe le fichier que vous mentionnez dans #include, dans le fichier .cpp où vous écrivez la directive #include. Cela signifie que si vous écrivez #include"add.h", vous obtenez la même chose, c'est comme si vous faisiez une "déclaration avant".

Je suppose que add.h a cette ligne:

int add(int x, int y); 
0

un additif rapide concernant: habituellement vous mettre ces références avant dans un fichier d'en-tête appartenant au .c (pp) fichier où la fonction/variables etc. est mis en œuvre. dans votre exemple, il ressemblerait à ceci: add.h:

extern int add(int a, int b); 

le mot-clé états externat que la fonction est en fait déclarée dans un fichier externe (pourrait également être une bibliothèque, etc.). votre main.c ressemblerait à ceci:

 
#include 
#include "add.h" 

int main() 
{ 
. 
. 
. 

+0

Mais, ne le faites pas nous mettons seulement les déclarations dans le fichier d'en-tête? Je pense que c'est pourquoi la fonction est définie dans "add.cpp", et donc en utilisant des déclarations forward? Merci. – Simplicity

23

Le compilateur recherche chaque symbole utilisé dans l'unité de traduction en cours précédemment déclarée ou non dans l'unité en cours. C'est juste une question de style fournissant toutes les signatures de méthodes au début d'un fichier source alors que les définitions sont fournies plus tard. L'utilisation significative de celui-ci est lorsque vous utilisez un pointeur vers une classe en tant que variable membre d'une autre classe. Par conséquent, utilisez les déclarations forward dans les classes lorsque cela est possible. Si votre programme n'a que des fonctions (avec des fichiers d'en-tête), fournir des prototypes au début n'est qu'une question de style. Ce serait de toute façon le cas si le fichier d'en-tête était présent dans un programme normal avec en-tête qui n'a que des fonctions.

7

Le terme "déclaration avant" en C++ est surtout utilisé uniquement pour déclarations de classe. Voir (la fin de) this answer pour pourquoi une "déclaration avant" d'une classe est vraiment une simple déclaration de classe avec un nom fantaisie.

En d'autres termes, le « avant » ajoute simplement lest à terme, toute déclaration peut être considérée comme étant en avant dans la mesure où elle déclare un certain identifiant avant il est utilisé.

(Quant à ce qui est une déclaration par opposition à une définition, voir à nouveau What is the difference between a definition and a declaration?)

Questions connexes