Références

Les références de type sont une fonctionnalité nouvelle de C++ par rapport au langage C. Nous en verrons de très intéressants usages en programmation orientée objet, mais il n’ont pas de rapport direct avec elle.

Nous avons dit qu’une donnée est une case de la mémoire qui possède un certain type. Pour pouvoir parler de cette case, on peut soit utiliser son adresse en mémoire (qui est la méthode des pointeurs), soit faire référence directement à la donnée, ce qui est le cas quand on utilise un nom de variable.

Ainsi, après la déclaration suivante :

int i = 18;

le compilateur a réservé une case mémoire de deux octets pour la variable i, et chaque fois que dans le programme on parle de i, le compilateur sait qu’il doit utiliser cette case-là. Nous dirons que i est une référence pour la donnée correspondante de valeur 18.

En général, un seul nom suffit pour une case mémoire. Mais dans certains cas, on a besoin d’un autre nom, d’une seconde référence. Pour la créer et l’initialiser, on utilise l’opérateur de référence & (à ne pas confondre avec l’opérateur binaire de « et » logique; il s’agit d’un opérateur unaire ici) :

int& j = i;

Ce qui signifie : j est une référence sur un entier (type int&) qui est équivalente à i. Dès lors, tous les usages de j dans la suite du programme seront équivalents à ceux de i. Par exemple, si l’on écrit :

int i = 18;
int& j = i;
i++;
j++;

À la fin de ces instructions, i et j seront tous deux égaux à 20. Plus exactement, l’unique case mémoire dénommée à la fois i et j contiendra la valeur 20.

Les références servent rarement directement. En général, on les utilise dans des paramètres de fonctions, ou des résultats, ainsi qu’en programmation orientée objet. Nous en verrons de nombreux exemples plus loin.

Signalons d’autre part que l’on peut indifféremment écrire :

int& j = i;

ou :

int &j = i;

La première écriture signifie « j est du type référence sur un entier int&, et équivaut à i » , tandis que la seconde signifie « &j, c’est-à-dire la case mémoire référencée par j, est de type entier, et est la même que i » ; ces deux significations sont équivalentes, si bien qu’on peut même écrire :

int&j = i;

ce qui est toutefois peu clair. La seconde écriture précédente est la plus utilisée en pratique, car elle permet de déclarer simultanément entier et référence sur entier, comme ceci :

int i = 18, &j = i;

On tient compte dans ce cas que les initialisations se font de gauche à droite.

Il est important de comprendre qu’il n’existe aucune opération sur les références, car il serait impossible au compilateur de différencier une telle opération de celle agissant sur le type de base. C’est pourquoi, lorsqu’on écrit j++ par exemple, l’opérateur ++ s’applique à la donnée de type int dont j est une référence, et non au type int&.

En particulier, il n’y a pas d’opérateur d’affectation donnant une référence. Par conséquent, une référence ne peut être modifiée, et ne prend de valeur qu’au moment de son initialisation. Pour cette dernière raison, l’initialisation d’une référence est obligatoire. L’écriture :

int &j;                    // incorrect

est refusée par le compilateur (Error : Object must be initialized, l’objet doit être initialisé).

Il est parfaitement possible par contre d’initialiser une référence sur une constante, ou sur une variable d’un type différent. Par exemple, on peut écrire :

int &k = 10;

Dans ce cas, une variable temporaire de type int est créée et initialisée à 10, avant que k ne devienne une référence de cette variable. Le code précédent équivaut donc à :

int provisoire = 10;
int &k = provisoire;

Fonctionnellement, un tel code ne diffère pas de :

int k = 10;

ce qui signifie que les références sur des constantes n’ont aucun intérêt direct (mais elles sont très utiles indirectement dans les appels de fonctions, comme on le verra).

Le comportement est identique lorsqu’on initialise une référence avec une expression, ou une variable d’un type différent de celui indiqué dans l’expression, même proche. Ainsi, l’écriture suivante :

unsigned u = 1;
int &k = u;

créera une variable intermédiaire comme précédemment; il en résulte que les modifications de k et u seront tout à fait indépendantes, c’est pourquoi le compilateur vous prévient alors par un message d’attention Warning : Temporary used to initialize 'k', une variable temporaire est utilisée pour initialiser ‘k’.

Lorsqu’on spécifie un changement de type, le comportement n’est pas clair et dépend manifestement du bon vouloir du compilateur :

int &k = (int) v;

sera toujours accepté par Turbo C++, sans erreur ni avertissement, si la variable v peut être convertie en un entier. Cependant, ce n’est que si v est du type unsigned, long ou unsigned long, ou encore de l’un des types short, et évidemment int, qu’aucune variable temporaire n’est créée. Par contre, si v est du type char, ou de l’un des types décimaux, une variable temporaire est créée mais non signalée, à la suite d’un bogue dans Turbo C++... Autant éviter ces écritures par conséquent, et utiliser franchement des pointeurs. Notons que depuis le spécification 2.1 de C++, de telles écritures sont interdites.

Naturellement, on peut créer autant de types référence qu’il y a de types possibles : long&, float&, double&, etc.

Précédent Précédent Sommaire Sommaire Suivant Suivant