Definition / declaration ✱

  • Your native language: with Chrome, right-click on the page and select « Translate into … »

  • English version:

Le langage C++ s’est construit au fil du temps et pas toujours dans un souci d’harmonisation. Ainsi ce chapitre qui aborde les déclarations et les définitions en C++ amène tout un lot de cas particuliers et de dérogations liées à certains mots-clefs. Nous avons volontairement choisi comme pédagogie de mettre l’accent sur quelques grands principes. Pour cela, nous nous plaçons dans un cadre idéal d’un programme comportant un seul fichier source sans aucune inclusion.

Principe général

Dans cette page, nous choisissons de désigner par le terme entité : une variable, une fonction, une structure…

Définition et déclaration en C++

Avertissement

Dans l’univers du C++, déclaration et définition sont deux notions proches mais cependant différentes. Faites très attention, car dans d’autres langages, ces deux termes sont souvent utilisés de manière équivalente.

D’après la terminologie du langage C++, une déclaration sert à :

  • Introduire ou réintroduire une entité dans un programme en rappelant son type.

Une définition correspond à :

  • Une déclaration

  • Accompagnée de suffisamment d’informations pour que le compilateur puisse créer cette entité.

Note

Vous remarquez qu’une définition est aussi une déclaration ! On peut aussi voir une déclaration comme une définition tronquée.

Voici une règle commune à de nombreux langages :

Règle - Declaration-before-use : une entité doit être déclarée (ou définie) avant de pouvoir être utilisée

Dans le langage C++, le principe intitulé One Definition Rule s’applique :

Règle ODR - One Definition Rule : dans un fichier source, on doit trouver une et une seule définition par entité

Note

Durant la compilation d’un fichier source, si le compilateur trouve deux définitions, il émet une erreur de redéfinition.

Note

Imposer une seule définition par entité tient du bon sens. En effet, comment le compilateur devrait-il réagir s’il trouvait dans le même fichier source deux fonctions toto() effectuant des traitements différents !

Note

La règle ODR concerne les définitions. Cependant, il n’y a pas de limite sur le nombre de déclarations associées à une même entité tant qu’elles restent identiques (non contradictoires).

Exercice

Dans le cas d’un unique fichier source présent dans le projet, indiquez si les affirmations suivantes sont vraies ou fausses :

  1. En C++, définition et déclaration signifient la même chose.

  2. La règle ODR indique qu’il faut que chaque entité soit définie au moins une fois durant la compilation.

  3. Une déclaration introduit ou réintroduit un nom et un type.

  4. On peut trouver plusieurs déclarations d’une même entité dans un fichier source.

  5. Si une déclaration est présente, la définition devient optionnelle.

  6. On peut utiliser un nom n’importe où dans le fichier source sans risque d’erreur.

Les variables

Vous pouvez retrouver une présentation sur les variables dans la référence sur le C++.

Syntaxe

Définition d’une variable :

  1. Type Nom; // sans initialisation

  2. Type Nom = ValeurInitiale; // avec initialisation

Définir une variable sans l’initialiser est risquée et doit être réservée à de très rares occasions.

Note

Les syntaxes à base d’accolades : int a = {40}; ou int a{40}; ou int a = {}; ou int a{}; sont hors programme.

Exemple

int anInteger;                  // definition of an integer variable without initialization
double aFloatingNumber = 3.0;   // definition of the variable aFloatingNumber of type double with initialization

Avertissement

La déclaration de variable est hors programme

Initialisation des variables par défaut

Pour commencer, nous abandonnons une croyance assez tenace chez les élèves :

NON, les variables ne sont pas initialisées par défaut en C++.
NON, les variables numériques ne sont pas initialisées à 0 en C++.

Nous allons vérifier cette affirmation au moyen du programme suivant : créons un entier sans l’initialiser et affichons sa valeur, recommençons ce test plusieurs fois.

#include <iostream>

int main()
{
        for (int i = 0; i < 100 ; i++)
        {
                int a;
                std::cout<<a;
        }
        return 0;
}

>> 0 0 0 0 0 0 0 ...

Avertissement

Sur 100 essais, vous obtiendrez probablement toujours la valeur 0. Mensonge, il y aurait une initialisation masquée !! Cette fausse impression est légitime, mais il y a une raison à cela. En effet, lors du lancement de votre programme, son espace mémoire est mis à zéro afin d’effacer toute trace d’informations provenant d’autres programmes. Ainsi, en début de programme, statistiquement, les variables numériques ont de forte chance d’occuper un espace mémoire contenant la valeur 0. Mais c’est un coup de chance si l’on peut dire ! Après un certain temps, le programme alloue de nouvelles variables en mémoire en lieu et place des anciennes variables et là, les valeurs ne sont pas forcément nulles !

En modifiant le programme précédent pour qu’il altère la case mémoire de la variable a, on obtient un résultat beaucoup plus amusant :

int main()
{
        for (int i = 0; i < 100 ; i++)
        {
                int a;
                a  += 1;
                std::cout<<a<<" ";
        }
        return 0;
}

>> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35...

Cette exemple montre que la variable a lorsqu’elle est créée réutilise la case mémoire de la précédente variable a sans l’initialiser.

Avertissement

L’utilisation d’une variable non initialisée ne produit pas de message d’erreur. Il est possible de forcer le compilateur à déclencher une erreur en utilisant les paramètres de compilation supplémentaires -Wall -Werror.

Convention : tant qu’une variable n’est pas initialisée, il ne faut pas l’utiliser car son contenu est indéterminée.

Masquage

Pour une variable, la règle ODR s’applique à l’intérieur du bloc d’accolades où elle a été créée. Cependant, dans un sous-bloc d’accolades, vous pouvez redéfinir une variable portant le même nom qu’une variable précédente. Dans ce cas, la variable dans ce sous-bloc masque la précédente :

#include <iostream>

int main()
{
        int x = 2;
        {
                int x = 3;              // this new variable x hides the one from the outer block
                std::cout << x << " ";
        }
        return 0;
}
>> 3

Cette situation est à éviter car elle nuit fortement à la lisibilité/compréhension du programme.

Exemples d’erreurs

L’utilisation d’une variable avant toute définition produit une erreur de la forme : « le nom X n’est pas déclaré ». Voici un exemple :

#include <iostream>
int main()
{
        std::cout << a;
        return 0;
}

Le redéfinition d’une variable, même à l’identique, produit une erreur de compilation de la forme : « erreur de redéclaration ». Voici un exemple :

int main()
{
        int a = 2;
        int a = 2;
        return 0;
}

Exercice 1

1    a = 0;
2    int a;

Exercice 2

1    int a = 2;
2    a = 4;

Exercice 3

1    int a;
2    int b = c;
3    int c;

Exercice 4

1    int a = 2;
2    int i = 3;
3    int c;
4    int d = a + j;

Le compilateur parcourt le code source de haut en bas. Dès qu’il rencontre un problème : identificateur inconnu, variable non initialisée… il s’arrête et envoie un message d’erreur. Pour chaque exemple, indiquez la ligne où se trouve l’erreur ou 0 si tout est correct :

Exercice 1

Exercice 2

Exercice 3

Exercice 4

Indiquez si les affirmations suivantes sont vraies ou fausses :

  1. La définition d’une variable donne son nom et son type.

  2. Le compilateur C++ accepte d’utiliser une variable non initialisée.

  3. En C++, les numériques sont automatiquement initialisées à la valeur 0.

  4. Deux définitions du même nom sont possibles si les types sont identiques.

  5. On peut utiliser une variable après sa définition.

Les fonctions

Syntaxe

La spécification du C++ sur les fonctions nous informe que :

  • La déclaration d’une fonction permet d’indiquer son nom et son type (type de retour et type des arguments)

  • La définition d’une fonction est une déclaration suivie d’un corps de fonction (function body) délimité par une paire d’accolades { } et contenant l’ensemble des traitements effectués par cette fonction.

Déclaration d’une fonction :

TypeRetour NomFonction(Type1 NomParametre1, Type2 NomParamètre2);

Définition d’une fonction :

TypeRetour NomFonction(Type1 NomParametre1, Type2 NomParamètre2)
{
// instructions
}

Note

Les fonctions ne retournant pas de valeur doivent retourner le type void.

Forward declaration

Si on ne disposait pas des déclarations, cela obligerait les programmeurs à organiser les fonctions dans un certain ordre dans le code. Par exemple, si une fonction A() appelait une fonction B(), la définition de la fonction B() devrait alors être placée avant la définition de la fonction A() dans le source.

Heureusement, l’utilisation des déclarations permet de ne plus avoir à se soucier de l’ordre dans lequel les définitions des fonctions A() et B() doivent se trouver. En effet, il suffit d’écrire une déclaration (Forward declaration) de la fonction B() avant toute utilisation de cette fonction dans le programme. Voici un exemple :

void B();          // Forward declaration of function B

void A()           // definition of function A
{
   B();            // function B can be called because it was declared beforehand
}

void B()           // the definition of function B is placed after A thanks to the forward declaration mechanism
{
   ...
}

Exemples d’erreur

L’utilisation d’une fonction avant toute définition ou déclaration produit une erreur de la forme : « le nom n’est pas déclaré ». Voici un exemple :

int main()
{
        F();            // ERROR: "name F is not declared"
        return 0;
}

void F() {}

Le redéfinition d’une fonction, même à l’identique, produit une erreur de compilation de la forme : « Le nom a déjà été défini ». Voici un exemple :

void F() {}
void F() {}             // ERROR: The name F has already been defined

int main()
{
        F();
        return 0;
}

La déclaration d’une fonction sans définition produit une erreur de la forme « aucune définition trouvée » :

void F();

int main()
{
        F();
        return 0;
}

Indiquez si les affirmations suivantes sont vraies ou fausses :

  1. En C++, définition et déclaration de fonctions signifient la même chose.

  2. Si la fonction ne retourne pas de valeur, son type de retour est absent.

  3. Une définition de fonction doit inclure un corps de fonction.

  4. Un corps de fonction est délimité par une paire de crochets.

  5. Un corps de fonction se termine par un point virgule ;

  6. Un appel de fonction sans déclaration préalable déclenche une erreur.

  7. La syntaxe d’une déclaration de fonction se termine par une parenthèse )

  8. Si le compilateur ne trouve aucune définition, il n’émet pas d’erreur.

  9. Si le compilateur trouve deux définitions identiques dans le même fichier, il n’émet pas d’erreur.

Les structures

Nous rappelons qu’un type structure désigne un type composé. Il s’agit d’une classe dont tous les membres sont publics par défaut.

Avertissement

Attention, définir un type structure nommée S et définir une variable de type S sont deux choses différentes. Dans le premier cas, on définit une classe dans l’autre on instancie un objet.

Syntaxe

On trouve :

  • La déclaration d’un type structure permet de déclarer son nom et son type (struct)

  • La définition d’un type structure inclut la déclaration suivie d’une liste de variables/fonctions présentes dans la structure. Cette liste est délimitée par une paire d’accolades et se termine par un ;

Déclaration d’une structure :

struct Nom;

Définition d’une structure :

struct Nom
{
Type1 var1;
Type2 fnt(Type3 var2) {…};
};

Avertissement

Après une déclaration, la structure peut être utilisée de manière très limitée. En effet, aucune information n’est donnée sur les membres internes et il n’est donc pas possible d’y accéder. On peut cependant créer des pointeurs vers cette structure.

Après sa déclaration, l’usage d’une structure reste très restreint : ses membres n’étant pas définis, ceci empêche tout accès aux variables et fonctions. Il demeure néanmoins possible de créer des pointeurs vers cette structure.

Exemples d’erreur

Si nous insérons deux fois une définition dans un même fichier, ceci produit une erreur de redéfinition :

struct A { int x; };
struct A { int x; };   // error: previous definition of struct A

int main() { }

Indiquez si les affirmations suivantes sont vraies ou fausses :

  1. Un type structure est un type.

  2. La syntaxe de la définition d’un type structure se termine par une accolade }

  3. Dans un même fichier, on peut définir plusieurs fois le même type structure.

Travail à rendre sur Github Classroom

Exercice 1a

Copiez cet exemple dans un fichier nommé 1a_decla.cpp

#include <iostream>
using namespace std;

void grape(){ cout<<"grape"<<endl; house(); }
void ivory(){ cout<<"ivory"<<endl; joker(); }
void moon() { cout<<"moon"<<endl; }
void chair(){ cout<<"chair"<<endl; dough(); }
void eagle(){ cout<<"eagle"<<endl; flame(); }
void joker(){ cout<<"joker"<<endl; }
void dough(){ cout<<"dough"<<endl; eagle(); grape(); }
void apple(){ cout<<"apple"<<endl; bread(); }
void house(){ cout<<"house"<<endl; ivory(); }
void flame(){ cout<<"flame"<<endl; star();  moon(); }
void bread(){ cout<<"bread"<<endl; chair(); }
void star() { cout<<"star"<<endl; }

int main(){ apple(); return 0; }

Objectif : Faites en sorte que ce programme compile sans utiliser de declaration de fonctions

Exercice 1b

Copiez cet exemple dans un fichier nommé 1b_decla.cpp

#include <iostream>
using namespace std;

void piano(){ cout<<"piano"<<endl; table(); }
void light(){ cout<<"light"<<endl; zebra(); }
void mango(){ cout<<"mango"<<endl; river(); }
void table(){ cout<<"table"<<endl; queen(); }
void stone(){ cout<<"stone"<<endl; mango(); }
void tiger(){ cout<<"tiger"<<endl; stone(); }
void zebra(){ cout<<"zebra"<<endl; }
void river(){ cout<<"river"<<endl; piano(); }
void cloud(){ cout<<"cloud"<<endl; tiger(); }
void queen(){ cout<<"queen"<<endl; light(); }

int main(){ cloud(); return 0; }

Objectif : Faites en sorte que ce programme compile en utilisant des declarations de fonctions (forward declaration)

PS: Vous ne devez pas toucher à l’ordre des lignes actuel :)