C++, à la différence de Pascal, Fortran, CAML et Java, dispose d'une
opération qui permet d'obtenir l'adresse d'un objet (ou d'une fonction).
Pour toute expression qui désigne un objet (ou toute expression
gauche, voir § 5), & est une expression dont la
valeur est l'adresse de l'objet désigné par . Si est
de type , alors &
est une expression de type *.
Notons la simplification suivante : &*p en p, si
p est d'un type pointeur.
Cette opération est essentielle en C pour simuler le passage des
arguments par référence, et beaucoup moins utilisée en C++, qui dispose
de ce mode. Si n est une variable (ou plus généralement une
expression qu'on peut placer à gauche d'une affectation) que l'on veut
faire modifier par une fonction f, on passe à f en
argument l'adresse &
n de n. On doit donc
déclarer que le paramètre correspondant de f est un pointeur
void f(int *p) { ... *p ...}
et appeler f(&n)
. Le passage de l'argument &n
se fait par
valeur, ce qui fait que l'adresse de n est copiée dans
p. Dans le corps de la fonction, à chaque fois que *p
est lue ou écrite, c'est donc la variable d'adresse qui est lue ou
écrite, c'est-à-dire n. Ce mode de passage des arguments est
appelé passage par adresse. On pourrait ainsi réécrire la
fonction swap, donnée au § 32 :
void swap(int *x, int *y) { int temp = *x; *x = *y; *y = temp; } int main() { int a=1, b=2; swap(&a, &b); cout << a << "\t" << b << endl; return 0; }
C'est la seule façon pour simuler le passage par référence en C. Le
passage par adresse de C est plus lourd que le passage par référence de
C++, mais il a au moins l'avantage d'être explicite du côté de l'appel :
quand on voit un appel swap(&a, &b)
, on sait qu'il s'agit d'un
appel par adresse, tandis qu'en C++, devant un appel swap(a, b)
,
il faut aller voir la déclaration de la fonction swap pour
savoir s'il s'agit d'un appel par référence ou d'un appel par valeur.
Il y a une autre différence importante entre l'appel par référence de C++ et l'appel par adresse de C. Dans l'exemple ci-dessous, le corps de la fonction peut modifier la valeur du paramètre p, en lui affectant une autre valeur que , de sorte que p pointe alors vers une autre variable que n, ce qui n'a pas l'effet attendu (ni sur n ni sur l'autre variable en question):
int m = 2, n = 3; void f(int *p) { p = &m; *p = 4; // ÉTRANGE : f modifie la valeur de m }
L'appel f(&n)
modifie toujours la valeur de la variable
m et non celle de la variable n. Pour éviter ce
risque, on devrait déclarer
void f(int *const p);
ce qui permettrait au compilateur de détecter une affectation p = .... On peut lire int *const p comme int* (const p), ce qui exprime que p est un pointeur constant. Ce comportement ne peut pas se produire avec le passage par référence de C++.
D'autre part, si un passage par adresse est souhaité seulement pour des raisons d'efficacité, pour éviter la copie d'un objet de grande taille, de façon analogue au passage par référence de C++, on doit déclarer également la fonction avec un const, mais dans une autre position :
void f(const int *p);
Ici, const int *p se lit comme (const int) *p qui exprime que *p est un entier constant. On peut même combiner les deux const pour spécifier que ni p ni *p ne peuvent être modifiés :
void f(const int *const p);