[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Officiels
> Programmation
> Apprenez à programmer en C++ ! > [Théorie] La Programmation Orientée Objet > Nouveautés pour les variables
> Lecture du tutoriel
Nouveautés pour les variables
Nous continuons notre tour d'horizon des nouveautés du C++ dans ce chapitre. Nous n'allons pas encore voir ici la POO, mais patience, ça ne saurait tarder
Nous nous intéresserons aux nouveautés relatives aux variables, c'est-à-dire à la gestion de la mémoire. Nous découvrirons entre autres le type bool, les modifications par rapport aux définitions de variables, les allocations dynamiques en C++ et les références.
Rien de bien difficile au programme donc, mais il s'agit de nouveautés importantes, donc à ne pas négliger.
On a découvert dès le début du cours de C qu'il existait un grand nombre de types de variable différents :
- int
- long
- float
- double
- char
- etc.
Le problème s'est posé lorsqu'on a voulu stocker des
booléens. Faute d'avoir un type de donnée spécialisé dans le stockage des booléens, on a fait comme la plupart des programmeurs C font : on a utilisé un type entier, comme int.
Or, si on utilise int pour les booléens, on risque de les confondre avec des variables destinées à stocker des nombres, puisque int est à la base fait pour stocker des nombres !
Petit exemple tout simple :
Code : C1
2 | int majeur = 1;
int age = 21;
|
La variable
majeur est un booléen, car elle signifie soit vrai soit faux.
La variable
age, elle, est un nombre. Elle peut valoir par exemple 21.
Mais comment fait-on pour savoir laquelle de ces variables est un booléen et laquelle est un nombre ?
On peut se baser sur le nom de la variable, c'est sûr, mais il aurait été plus pratique et plus clair d'avoir un type spécial pour les booléens.
Ca tombe bien ! Il y a justement en C++ un nouveau type de base : le type
bool. Toute variable de ce type peut prendre 2 valeurs :
- true, qui signifie vrai.
- false, qui signifie faux.
(je vous conseille de retenir ces 2 valeurs par coeur, vous en aurez besoin
)
Du coup, le code qu'on a vu plus haut s'écrirait comme ceci en C++ :
Code : C++1
2 | bool majeur = true;
int age = 21;
|
Voilà une bonne chose qui nous permettra d'éviter des ambigüités dans nos programmes
Il n'y a pas de guillemets autour de true, car c'est un mot-clé du langage C++. Ce n'est pas une chaîne de caractères !
Rappel : les booléens dans les conditions
Je tiens juste à vous faire un petit rappel. Si vous avez bien suivi le cours de C, ça ne devrait pas vous choquer
En théorie, on peut tester un booléen comme ceci dans une condition :
Code : C++1
2
3
4 | if (majeur == true) // S'il est majeur (forme longue)
{
// ...
}
|
Mais en général, si la variable a un nom clair, on préfèrera enlever la partie
== true. C'est tout à fait possible et l'ordinateur le comprend très bien :
Code : C++1
2
3
4 | if (majeur) // S'il est majeur (forme courte)
{
// ...
}
|
Ce code est plus lisible et plus court que le précédent. On comprend bien que la condition est "
S'il est majeur".
Par ailleurs, le point d'exclamation sert à exprimer la négation. Dans notre cas, ce code signifierait "
S'il n'est pas majeur" :
Code : C++1
2
3
4 | if (!majeur) // S'il n'est PAS majeur
{
// ...
}
|
Ce n'est pas une nouveauté du C++ car ça existait déjà en C, mais je tenais juste à vous informer que cette technique fonctionnait toujours avec le type
bool
En C, les variables devaient être déclarées (= créées) au début des fonctions. Vous avez vu cela dans le chapitre sur les variables au tout début du cours
Vous deviez donc faire toutes vos déclarations avant de commencer les instructions :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | void maFonction()
{
// D'abord on déclare les variables
double prixOrigine = 0.0;
double prixAchat = 0.0;
double difference = 0.0;
FILE* fichier = NULL;
// Ensuite on peut exécuter des instructions, des appels de fonction, etc.
fichier = fopen("exemple.txt", "r");
if (fichier != NULL)
{
fonction();
fscanf(fichier, "%lf", &prixOrigine);
fscanf(fichier, "%lf", &prixAchat);
difference = prixAchat - prixOrigine;
// etc.
}
}
|
La nouveauté en C++, c'est que l'on peut désormais déclarer des variables
n'importe où dans une fonction. C'est plus pratique lorsqu'on programme, ça nous évite d'avoir à remonter au début de la fonction si on n'a besoin d'une variable qu'à un moment de la fonction. Cela peut aussi améliorer la lisibilité du code surtout dans de grosses fonctions.
On pourrait donc écrire le code précédent comme ceci en C++ :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | void maFonction()
{
FILE* fichier = NULL;
fichier = fopen("exemple.txt", "r");
if (fichier != NULL)
{
fonction();
double prixOrigine = 0.0; // Déclaration au milieu
fscanf(fichier, "%lf", &prixOrigine);
double prixAchat = 0.0; // Autre déclaration au milieu
fscanf(fichier, "%lf", &prixAchat);
double difference = prixAchat - prixOrigine; // Encore autre déclaration au milieu
// etc.
}
}
|
Avec une version récente du langage C, il est aussi possible de déclarer une variable en plein milieu d'une fonction. Cependant, les programmeurs C préfèrent en général continuer à déclarer leurs variables au début des fonctions.
Précision importante : les variables ainsi créées sont
locales aux blocs où elles ont été déclarées. Je m'explique.
On dit que les accolades { et } délimitent des
blocs. Dans le code ci-dessus, vous devriez en voir deux : la fonction et le bloc if. Comme la variable
prixAchat a été déclarée dans le bloc if, elle sera supprimée à la fin du bloc if. Si elle avait été déclarée au début de la fonction en revanche, elle aurait été accessible dans toute la fonction.
Voilà, c'est assez simple à comprendre mais il faut le savoir ! La variable est détruite à la fin du bloc dans lequel elle a été déclarée.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | void maFonction()
{
FILE* fichier = NULL;
fichier = fopen("exemple.txt", "r");
if (fichier != NULL)
{
fonction();
double prixOrigine = 0.0; // Création de prixOrigine
fscanf(fichier, "%lf", &prixOrigine);
double prixAchat = 0.0; // Création de prixAchat
fscanf(fichier, "%lf", &prixAchat);
double difference = prixAchat - prixOrigine; // Création de difference
} // Destruction automatique de prixOrigine, prixAchat et difference
} // Destruction automatique de fichier
|
Déclaration dans une boucle
Dans le même ordre d'idée, il y a une nouveauté
vraiment très pratique (comprenez : je m'en sers tout le temps

). On peut déclarer une variable directement
dans une instruction for.
Prenons un exemple. Vous codez votre programme, tout va bien. Puis à un moment, pour une raison ou une autre, vous avez besoin de faire une boucle qui se répète 10 fois. Vous allez sûrement faire un for. Mais pour boucler 10 fois, vous aurez besoin d'une variable de boucle qui va retenir le nombre de tours de boucle (quand on n'est pas inspiré on appelle en général cette variable
i ).
En C, c'est un peu embêtant parce qu'il faut remonter au début de la fonction pour rajouter la déclaration de la variable. En plus, on ne sait pas trop quand elle sera utilisée en lisant la déclaration :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | void maFonction()
{
int i = 0;
/* Plein de code
....
....
*/
for (i = 0 ; i < 10 ; i++)
{
}
}
|
La nouveauté en C++, c'est que vous pouvez déclarer votre variable i directement dans l'instruction for. Elle sera détruite à la fin de la boucle, quand vous n'en aurez plus besoin.
Avantages : vous n'avez pas à remonter au début de la fonction pour déclarer la variable, et celle-ci est automatiquement détruite à la fin de la boucle. Pas d'utilisation inutile de la mémoire.
Le code C++ ressemblera donc à cela :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12 | void maFonction()
{
/* Plein de code
....
....
*/
for (int i = 0 ; i < 10 ; i++) // Déclaration de i
{
} // Destruction automatique de i
}
|
Ca n'a l'air de rien, mais je vous assure qu'en pratique quand on programme, ça c'est vraiment génial

Vous me verrez donc le faire la plupart du temps dans la suite du cours.
Si je vous dis "malloc" et "free", ça vous rappelle de joyeux souvenirs non ?
Code : C 1
2
3
4
5
6
7
8
9
10 | int main()
{
int *variable = NULL;
variable = malloc(sizeof(int)); // Allocation de mémoire
free(variable); // Libération de mémoire
return 0;
}
|
L'allocation dynamique est une technique qui permet de gérer vous-même l'allocation de mémoire pour vos variables. C'est notamment très pratique dans le cas de l'allocation de tableaux dont on ne connaît pas la taille avant compilation (revoyez le
chapitre sur l'allocation dynamique au besoin !).
En C++, les allocations dynamiques existent toujours et on en fait toujours. D'ailleurs, les fonctions malloc et free sont toujours utilisables. Cependant, le C++ dispose de nouveaux opérateurs spécialisés dans les allocations dynamiques :
new et
delete.
new et delete sont des opérateurs, des mots-clé du langage C++. Contrairement à malloc et free, ce ne sont pas des fonctions.
new et delete font en fait eux-mêmes appel aux fonctions malloc et free (on n'a pas réinventé la roue). Cependant, ils font aussi des tests et des initialisations supplémentaires, ce qui fait qu'on préfèrera toujours utiliser new et delete au lieu de malloc et free. Ils sont plus adaptés en C++.
Allocation dynamique d'une variable
new et delete ne s'utilisent pas exactement de la même manière que malloc et free.
On va dans un premier temps apprendre à s'en servir pour allouer une variable simple, puis on verra ensuite le cas de l'allocation de tableaux.
On souhaite donc allouer dynamiquement une variable (de type int par exemple).
En C++, on va d'abord devoir créer le pointeur et l'initialiser à NULL, ça on n'y coupe pas :
Code : C++
Allocation de mémoire
L'allocation de mémoire avec new se fait comme ceci :
Code : C++1 | variable = new int; // Allocation dynamique
|
Comparé à la "version C", il n'y a pas photo

On n'a plus besoin d'utiliser l'opérateur sizeof() du C. Ici, on indique juste le type de variable à créer.
Libération de mémoire
Lorsque vous avez fini d'utiliser votre variable et que vous n'en avez plus besoin, vous devez la libérer avec l'opérateur delete. Ultra-simple :
Code : C++1 | delete variable; // Libération de mémoire
|
new et delete étant des opérateurs, et non des fonctions (désolé d'insister

), on ne met pas de parenthèses.
Résumé
En résumé, voici à quoi ressemble un code d'allocation / libération de mémoire en C++ :
Code : C++ 1
2
3
4
5
6
7
8
9
10 | int main()
{
int *variable = NULL;
variable = new int; // Allocation de mémoire
delete variable; // Libération de mémoire
return 0;
}
|
Allocation dynamique d'un tableau
Si on veut allouer un tableau, l'opération est là encore très simple. On n'a plus besoin de faire un calcul du type 20 * sizeof(int) comme on devait le faire en C.
On commence par créer le pointeur :
Code : C++
Allocation de mémoire
Ensuite, l'allocation se fait comme ceci :
Code : C++1 | tableau = new int[20]; // Allocation de mémoire (20 cases)
|
Dans ce cas, un tableau de 20 cases sera alloué. Bien entendu, il est aussi possible de remplacer ce nombre par une variable :
Code : C++1 | tableau = new int[taille]; // Allocation de mémoire ("taille" cases)
|
La longueur du tableau sera définie par la valeur de la variable
taille.
Libération de mémoire
Lorsque vous n'avez plus besoin du tableau, vous devez le libérer... avec cette fois l'opérateur delete[] pour bien préciser qu'il s'agit d'un tableau. Vous n'avez pas besoin de préciser la taille entre crochets, mais n'oubliez pas ces crochets ils sont importants.
Code : C++1 | delete[] tableau; // Libération de mémoire
|
Résumé
Code : C++ 1
2
3
4
5
6
7
8
9
10 | int main()
{
int *tableau = NULL;
tableau = new int[20]; // Allocation de mémoire (tableau)
delete[] tableau; // Libération de mémoire (tableau)
return 0;
}
|
Vous souvenez-vous du chapitre sur les
structures et énumérations ?

On y avait appris à créer nos propres types de variables. On avait notamment utilisé l'exemple d'une structure nommée
Coordonnees :
Code : C1
2
3
4
5 | struct Coordonnees
{
int x;
int y;
};
|
Le problème des structures en C, c'est qu'il fallait placer le mot-clé struct au début de chaque déclaration d'une variable de type personnalisé :
Code : C1 | struct Coordonnees point;
|
Pour éviter d'avoir à répéter ce mot à chaque déclaration, on avait découvert l'instruction
typedef qu'on utilisait comme ceci avant la définition de notre structure :
Code : C1
2
3
4
5
6
7 | typedef struct Coordonnees Coordonnees; // typedef permet d'éviter d'avoir à taper "struct" à chaque déclaration
struct Coordonnees
{
int x;
int y;
};
|
Du coup, on pouvait déclarer une variable sans avoir à écrire
struct devant :
Code : C1 | Coordonnees point; // Le mot-clé struct est inutile grâce au typedef
|
La nouveauté
En C++, qu'on se rassure, les structures existent toujours (il y a même encore mieux, mais n'anticipons pas

).
La nouveauté du C++, c'est que le typedef est désormais automatique. A chaque fois que l'on déclare une structure (ou une énumération), un typedef est réalisé automatiquement par le compilateur. On peut donc n'écrire que l'instruction de déclaration de la structure :
Code : C++1
2
3
4
5
6
7 | // Le typedef est réalisé automatiquement par le compilateur, pas besoin de l'écrire
struct Coordonnees
{
int x;
int y;
};
|
Grâce à cela, le mot-clé
struct devient totalement inutile lors d'une déclaration de variable :
Code : C++1 | Coordonnees point; // Le mot-clé struct est inutile grâce au typedef automatique
|
Nous arrivons maintenant au point le plus important (et délicat) de ce chapitre. Ouvrez grandes vos oreilles (ou plutôt vos yeux

).
Le C++ introduit un nouveau concept :
les références.
Une référence est un synonyme d'une autre variable. On verra ce que ça veut dire un peu plus loin

Vous allez voir que les références ressemblent beaucoup aux pointeurs. Elles ont en effet été créées pour simplifier l'utilisation des pointeurs. Attention toutefois : je vous préviens qu'au début vous risquez de confondre les références avec les pointeurs (c'est assez perturbant quand on voit ça la première fois j'avoue

).
Les références à l'intérieur d'une fonction
Pour créer une référence, on doit utiliser le symbole & dans la déclaration :
Code : C++
Attention à ne pas confondre !
Dans une déclaration, le symbole & signifie "Je veux créer une référence" (c'est ce qu'on découvre maintenant). Partout ailleurs, le symbole & signifie "Je veux obtenir l'adresse de cette variable" (ça on l'avait déjà vu).
On confond facilement quand on débute. Il faut dire que les programmeurs n'ont pas été très malins en réutilisant le symbole & ici, y'a rien de tel pour confondre

Quand vous voyez un & désormais, vérifiez s'il se trouve dans une déclaration : si c'est dans une déclaration, c'est qu'on cherche à créer une référence, sinon c'est qu'on demande à obtenir l'adresse de la variable.
Bon, on a créé une référence. Et alors ?
Et alors si vous compilez le code ci-dessus, le compilateur va vous insulter poliment :
Citation : Compilateur C++error: 'referenceSurAge' declared as reference but not initialized
Si vous lisez l'anglais (et si vous ne le lisez pas vous devriez), vous avez compris le problème : le compilateur veut qu'on initialise immédiatement la référence.
Et ça c'est très important : une référence doit être immédiatement initialisée dès le début, contrairement aux pointeurs. Et ce n'est pas tout : une fois initialisée, la référence ne pourra plus changer !
Il y a donc deux règles que j'aimerais que vous reteniez par coeur :
- Règle 1 : une référence doit être initialisée dès sa déclaration.
- Règle 2 : une fois initialisée, une référence ne peut plus être modifiée.
Initialisation d'une référence
On va donc initialiser notre référence.
Comme je vous l'ai dit un peu plus tôt, une référence est un synonyme d'une autre variable. Cela veut donc dire qu'il faut créer une autre variable pour y trouver un minimum d'intérêt
Allez hop, il est l'heure de ressortir la bonne vieille variable qui a fait ses preuves : la variable... age !
(le premier qui ose dire que je fais des cours pas originaux il va tâter de mon sabre
)
Code : C++1
2 | int age = 21; // Déclaration de la variable age (rien de nouveau)
int &referenceSurAge = age; // Déclaration et initialisation d'une référence sur la variable age
|
Pour initialiser une référence, vous avez juste besoin d'écrire le nom de la variable dont elle sera le synonyme. Pas besoin d'écrire &age comme on le faisait avant avec les pointeurs.
Je vous avais prévenu, vous risquez de confondre avec les pointeurs.
Je vous ferai un résumé comparatif pointeurs / références un peu plus loin pour que vous puissiez bien les comparer.
Utilisation de la référence
Bon, maintenant notre référence est créée. On a un synonyme de la variable age.
Comment on s'en sert concrètement ? Exactement comme la variable age ! Pas besoin de mettre une étoile * devant pour dire qu'on veut obtenir la valeur. Les références permettent, vous allez le voir, de simplifier l'écriture de nos programmes pour éviter au maximum les erreurs (un oubli d'une étoile est si vite arrivé !).
Regardez ce petit programme complet qui affiche la variable age, la modifie, et la réaffiche, le tout en passant par une référence :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | int main()
{
int age = 21;
int &referenceSurAge = age;
cout << referenceSurAge << endl;
cout << age << endl;
referenceSurAge = 40;
cout << referenceSurAge << endl;
cout << age << endl;
return 0;
}
|
Résultat :
Code : Console
Comme vous pouvez le voir, une référence s'utilise exactement comme la variable d'origine. C'est le compilateur qui fait la "conversion" et qui sait qu'il doit affecter la variable "age" lorsqu'on travaille avec la référence.
Comparatif pointeur / référence
En C++, les pointeurs existent toujours. Les références sont juste une alternative aux pointeurs. Elles ont surtout l'avantage d'être plus simples à utiliser, mais elles ne peuvent pas les remplacer complètement.
Pourquoi ? On l'a vu : une référence ne peut pas faire référence à une nouvelle variable une fois qu'elle a été initialisée. Un pointeur, lui, peut toujours pointer vers une nouvelle variable au cours de l'exécution du programme.
Il est
très courant de confondre les pointeurs et les références lorsqu'on débute (si ça peut vous rassurer, moi aussi j'ai pas mal confondu au début). Je vais donc vous donner 2 codes source : le premier utilise les pointeurs, le second les références. Si à un moment vous avez un doute et que vous vous mettez à confondre pointeurs et références, servez-vous de l'exemple ci-dessous pour vous assurer que vous faites les choses correctement :
| -------- Code d'exemple avec un pointeur -------- | -------- Code d'exemple avec une référence -------- |
|---|
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | int main()
{
int age = 21;
int *pointeurSurAge = &age;
cout << *pointeurSurAge;
*pointeurSurAge = 40;
cout << *pointeurSurAge;
return 0;
}
|
|
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | int main()
{
int age = 21;
int &referenceSurAge = age;
cout << referenceSurAge;
referenceSurAge = 40;
cout << referenceSurAge;
return 0;
}
|
|
Voilà, j'espère que ce comparatif vous permettra d'y voir plus clair

Ce qu'il faut retenir dans l'histoire, c'est que les références sont là pour
simplifier l'écriture du code source. Comme on n'a plus besoin d'utiliser le symbole * à chaque fois qu'on veut accéder à la variable age, on minimise les risques d'erreur dans nos programmes.
Les références vers des structures
Si vous faites une référence vers une structure, il faudra utiliser le symbole point "." et non le symbole flèche "->" lorsque vous voulez accéder à un élément d'une structure.
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | struct Coordonnees
{
int x;
int y;
};
int main()
{
Coordonnees point;
Coordonnees &referenceSurPoint = point;
referenceSurPoint.x = 10;
referenceSurPoint.y = 5;
cout << "x : " << referenceSurPoint.x << endl;
cout << "y : " << referenceSurPoint.y << endl;
return 0;
}
|
Code : Console
Une fois de plus, vous voyez qu'une référence s'utilise
exactement comme une variable
Les références lors d'un appel de fonction
Les codes qu'on a vus jusqu'ici n'étaient pas très utiles. En pratique, on n'est pas suffisamment maso pour créer des références juste "pour le plaisir" si elles ne sont pas indispensables.
En fait, comme pour les pointeurs, les références révèlent toute leur utilité lorsqu'on appelle une fonction.
Voyons voir ça dans un exemple :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | struct Coordonnees
{
int x;
int y;
};
void remiseAZero(Coordonnees &pointAModifier);
int main()
{
Coordonnees point;
remiseAZero(point); // Pas besoin d'indiquer l'adresse de point avec un & lors de l'appel
return 0;
}
void remiseAZero(Coordonnees &pointAModifier) // La fonction indique qu'elle récupère une référence
{
// La référence s'utilise exactement comme une variable
// On utilise donc des points "." et non des flèches "->"
pointAModifier.x = 0;
pointAModifier.y = 0;
}
|
On transmet la référence à la fonction RemiseAZero le plus simplement du monde, sans avoir à mettre de symbole &.
Code : C++
Le but des références est là encore très clair : éviter d'avoir à taper des symboles en plus pour minimiser les erreurs.
La fonction doit bien préciser qu'elle reçoit une référence. On doit donc placer le symbole & dans la déclaration (et dans le prototype) :
Code : C++1 | void remiseAZero(Coordonnees &pointAModifier)
|
Ensuite, à l'intérieur de la fonction, on se sert de la référence comme si c'était une variable (dans le cas présent, on utilise donc le symbole point et non la flèche -> ) :
Code : C++1
2 | pointAModifier.x = 0;
pointAModifier.y = 0;
|
Comparaison pointeur / référence
Une fois de plus, je crois qu'il est utile que je vous fasse un comparatif du même code utilisant d'un côté un pointeur, de l'autre une référence.
| ----------------------- Code d'exemple avec un pointeur ------------------- | ----------------- Code d'exemple avec une référence --------------------- |
|---|
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | int main()
{
Coordonnees point;
remiseAZero(&point);
return 0;
}
void remiseAZero(Coordonnees *pointAModifier)
{
pointAModifier->x = 0;
pointAModifier->y = 0;
}
|
|
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | int main()
{
Coordonnees point;
remiseAZero(point);
return 0;
}
void remiseAZero(Coordonnees &pointAModifier)
{
pointAModifier.x = 0;
pointAModifier.y = 0;
}
|
|
Ces codes fonctionnent tous deux très bien en C++. Autant que possible, on utilisera des références en C++, sauf quand l'utilisation d'un pointeur est obligatoire.
A retenir : s'il y a un code que vous devez retenir pour les références, c'est celui de l'appel d'une fonction utilisant une référence (celui que nous venons de voir). Dans 99,99% des cas, on utilise les références lorsqu'on fait appel à une fonction.
Que de nouveautés ! C'est le moins qu'on puisse dire

Et encore, vous n'avez pas tout vu ! Dans le prochain chapitre, nous découvrirons les nouveautés du C++ relatives aux fonctions.
Et après... après, je pense qu'on pourra commencer à parler de POO