Nous, qui connaissons maintenant les sous-programmes et les paramètres par valeur, avons entre les mains un coffre d'outils informatiques de plus en plus respectable. Frottons-nous de nouveau à un problème que nous avons déjà confronté plus tôt au cours du semestre.
Soit l'énoncé de problème suivant:
Problème: | veuillez écrire un programme C++ comprenant une fonction «Echanger()» qui prendra en paramètre deux entiers, X et Y, et qui échangera leurs valeurs, et une procédure «main()» qui l'appellera pour faire la démonstration que celle-ci fonctionne. |
Ce qui semble banal. Vous avez sûrement en tête à ce stade-ci le pseudo-code suivant:
Echanger (X,Y) Temporaire <-- X X <-- Y Y <-- Temporaire |
en raison duquel vous serez sans doute empressé(e) de produire en réponse le code visible ci-dessous (ou quelque chose de fort similaire).
#include <iostream> using namespace std; // Procédure: Échanger () // Intrants: deux entiers X et Y // Extrants: aucun // Rôle: Echange les valeurs de X et de Y void Echanger (int X, int Y); // Programme principal // Teste le fonctionnement de la procédure «Echange()» void main () { int a, b; a = 3; b = 5; cout << "Avant l'echange: a vaut " << a << " et b vaut " << b << endl; Echanger (a, b); cout << "Apres l'echange: a vaut " << a << " et b vaut " << b << endl; } // programme principal // Procédure: Échanger () // Intrants: deux entiers X et Y // Extrants: aucun // Rôle: Échange les valeurs de X et de Y void Echanger (int X, int Y) { int Temporaire; Temporaire = X; X = Y; Y = Temporaire; } // Echanger () |
Or: ce programme, si vous l'exécutez, vous présentera
la sortie suivante:
|
![]() |
Les valeurs des variables «a» et «b» n'ont pas été interverties. Surprenant? Examinons en détail l'exécution du programme, question de comprendre pourquoi.
a
|
b
|
|
---|---|---|
La procédure «Echanger()»
est appelée dans le programme principal, avec les paramètres
«a» et «b»
qui ont alors respectivement les valeurs 3
et 5.
|
3
|
5
|
L'appel de la procédure aura l'effet suivant:
La portée des variables «X» et «Y» se limite à la procédure «Echanger()». Il faut être attentif à ceci:
Ainsi, les modifications apportées aux variables «X»
et «Y» dans le cadre
de la procédure «Echanger()»
n'ont rien à voir avec les variables «a»
et «b» de la procédure
«main()». Ce sont
toutes des variables différentes.
![]() |
(1) Une fois
l'appel effectué, les valeurs des variables «a»
et «b» de la procédure
«main()» sont copiées
dans les paramètres «X»
et «Y» de «Echanger()».
La valeur de la variable «Temporaire»
est encore inconnue.
|
|
(2) Dans «Echanger()»,
la variable «Temporaire»
se voit affectée la valeur de la variable «X».
Rien ne change dans «main()».
|
![]() |
|
![]() |
(3) Dans «Echanger()»,
la variable «X»
se voit affectée la valeur de la variable «Y».
Rien ne change dans «main()».
|
|
(4) Dans «Echanger()»,
la variable «Y»
se voit affectée la valeur de la variable «Temporaire».
Rien ne change dans «main()».
|
![]() |
On voit donc qu'en aucun cas, une modification des variables «X» et «Y» de la procédure «Echanger()» n'affectera les variables «a» et «b» de la procédure «main()». Ceci parce que la procédure «Echanger()» accepte des paramètres passés par valeur.
Il nous faut donc une alternative lorsque c'est le contenu des variables utilisées comme paramètres à l'appel d'un sous-programme (les «originaux») qui doit être modifié par l'exécution du sous-programme, et non pas les copies.
Le titre de ce document vend un peu la mèche, mais voilà: la
solution réside en un autre type de paramètres--les paramètres
par référence.
Les paramètres par référence sont fondamentalement différents des paramètres par valeur, au sens où:
Sur le plan syntaxique, en C++, la seule différence entre un paramètre passé par référence et un paramètre passé par valeur est l'ajout du symbole «&» avant le nom du paramètre dans le cas d'un passage par référence.
Les prototypes des procédures «f()» et «g()», ci-après, illustrent cette nuance:
void f (int x);
|
Le paramètre «x»
est passé par valeur: «x»
sera une copie de la variable utilisée à l'appel de «f()»
|
void g (int &y);
|
Le paramètre «y»
est passé par référence: «y»
référera à la variable utilisée à
l'appel de «g()»
|
Si nous modifions notre solution au problème «Echange()» plus haut pour que cette procédure fasse usage de paramètres par référence, nous aurons donc le code visible ci-après.
#include <iostream> using namespace std; // Procédure: Échanger () // Intrants: X et Y, chacun étant une référence à un entier // Extrants: les références X et Y // Rôle: Echange les valeurs de X et de Y void Echanger (int &X, int &Y); // Programme principal // Teste le fonctionnement de la procédure «Echange()» void main () { int a, b; a = 3; b = 5; cout << "Avant l'echange: a vaut " << a << " et b vaut " << b << endl; Echanger (a, b); cout << "Apres l'echange: a vaut " << a << " et b vaut " << b << endl; } // programme principal // Procédure: Échanger () // Intrants: X et Y, chacun étant une référence à un entier // Extrants: les références X et Y // Rôle: Echange les valeurs de X et de Y void Echanger (int &X, int &Y) { int Temporaire; Temporaire = X; X = Y; Y = Temporaire; } // Echanger () |
Remarquez les très légères (mais combien importantes) différentes avec la solution précédente, qui utilisait des paramètres passés par valeur:
Chaque paramètre passé par référence doit être précédé d'un «&» dans le prototype de la fonction; d'ailleurs, un sous-programme peut avoir autant de paramètres par valeur et autant de paramètres par référence qu'elle en aura besoin, dans n'importe quel ordre.
Ainsi, la fonction suivante (aux noms fort peu significatifs, mais bon, l'idée est de faire une illustration, alors soyez tolérant(e)s s'il vous plaît):
int Faire_Quelquechose (float &Truc, int Machin, char &Chouette); |
est une fonction nommée «Faire_Quelquechose()» retournant un «int», et prenant en paramètre une référence à un «float», un «int» passé par valeur, et une référence à un «char».
Appeler un sous-programme se fait de la même façon qu'elle ait des paramètres passés par référence ou par valeur (ou un mélange des deux); il ne faut pas utiliser le «&» lors d'un appel de fonction pour passer un paramètre par référence. Le «&» dans ce contexte veut dire autre chose (voir ce cours pour plus de détails).
De même, dans la définition du sous-programme lui-même, si on fait omission de la déclaration des paramètres dans son prototype, rien ne change sur le plan syntaxique. C'est comme si, à part les «&» entre les parenthèses suivant le nom de la fonction, rien n'avait changé.
Mais il y a bien une différence; l'exécution
résultera en ce que vous voyez à droite... reste à
voir pourquoi.
|
![]() |
Examinons (très brièvement) ce qui se passe lors d'un appel de sous-programme avec paramètres passés par référence.
Remarquez que le schéma précédant n'accorde pas de valeur propre à la boîte de «X» et de «Y»; ceci veut illustrer que «X» et «Y» sont des références à d'autres variables (dans ce cas-ci: «a» et «b», respectivement). Modifier «X» signifie modifier l'objet auquel il réfère (ici: modifier «X» signifie modifier «a»).
Traçons l'exécution de la procédure «Echanger()»,
en examinant ce qui arrive maintenant avec chacune des variables en jeu dans
le cadre de chacune des procédures:
![]() |
(1) Une fois
l'appel effectué, les paramètres «X»
et «Y» de la procédure
«Echanger()» réfèrent
aux variables «a»
et «b» de «main()».
La valeur de la variable «Temporaire»
est encore inconnue.
|
|
(2) Dans «Echanger()»,
la variable «Temporaire»
se voit affectée la valeur de la variable «X»,
qui est celle de «a»
(la variable à laquelle «X»
réfère). Rien ne change dans «main()».
|
![]() |
|
![]() |
(3) Dans «Echanger()»,
la variable référée par «X»
se voit affectée la valeur de la variable référée
par «Y»; ainsi,
dans la procédure «main()»,
«b» reçoit
la valeur de «a».
|
|
(4) Dans «Echanger()»,
la variable référée par «Y»
se voit affectée la valeur de la variable «Temporaire».
L'échange de valeurs est complété.
|
![]() |