Passage de paramètres en C++
Il est important de comprendre précisément comment les arguments sont transmis à une fonction. Cela affecte la manière dont vous écrivez des fonctions et finalement comment elles fonctionnent. Il y a aussi un certain nombre de pièges à éviter.
En général, les arguments de la fonction doivent correspondre au type et à la séquence de la liste des paramètres de la définition de la fonction.
Si vous spécifiez un argument d'un type qui ne correspond pas au type du paramètre, le compilateur insère une conversion implicite de l'argument vers le type du paramètre lorsque cela est possible.
Les règles pour les conversions automatiques sont les mêmes que pour les conversions dans une instruction d'affectation.
Si ces conversions implicites entraînent une perte potentielle de précision, les compilateurs émettent généralement un avertissement.
Exemples de conversions risquées :
- long → int
- double → float
- int → float
Il existe trois mécanismes par lesquels les arguments sont passés aux fonctions :
- Passage par valeur
- Passage par adresse
- Passage par référence
1. Passage par valeur
Passage par valeur
Avec le mécanisme de passage par valeur, les valeurs des variables ou des constantes que vous spécifiez comme arguments ne sont pas du tout transmises à une fonction. Au lieu de cela, des copies des arguments sont créées, et ces copies sont transférées à la fonction.
Conséquences :
- Modifier le paramètre n'affecte pas l'original
- Le passage d'un objet volumineux sera lent (car copié)
Exemple 1 : Passage par valeur
#include <iostream>
using namespace std;
int changer(int); // prototype
int main(void){
int a {5}, res;
res = changer(a);
cout << "Val de a Après l'exécution de la fonction : " << a << '\n';
cout << "Valeur retournée par la fonction : " << res << '\n';
return 0;
}
int changer(int a) // définition de la fonction
{
a += 4;
cout << "Val de a dans la fonction : " << a << '\n';
return a;
}Val de a dans la fonction : 9 Val de a Après l'exécution de la fonction : 5 Valeur retournée par la fonction : 9
Explication :
- La variable
adansmain()est initialisée à 5. - Lors de l'appel
changer(a), une copie de la valeur 5 est passée à la fonction. - Dans
changer(), la copie (locale) est modifiée (5+4=9). - La variable originale dans
main()reste inchangée (5). - La fonction retourne la valeur modifiée (9).
Le passage par valeur est le mécanisme par défaut. Il offre une grande sécurité à la fonction appelante en empêchant la fonction de modifier les variables qui lui appartiennent.
Mais que faire si on souhaite modifier des valeurs dans la fonction appelante ?
2. Passage par adresse (avec pointeurs)
Passage par adresse
Lorsqu'un type de paramètre de fonction est un pointeur, le mécanisme de passage par valeur fonctionne comme auparavant. Cependant, un pointeur contient l'adresse d'une autre variable ; une copie du pointeur contient la même adresse et pointe donc vers la même variable.
Principe : On passe l'adresse de la variable, ce qui permet à la fonction de modifier la variable originale via le pointeur.
Exemple 2 : Passage par adresse (avec pointeur)
#include <iostream>
using namespace std;
int changer(int*); // prototype (attention: int*)
int main(void){
int a {5}, res;
res = changer(&a); // on passe l'adresse de a
cout << "Val de a Après l'exécution de la fonction : " << a << '\n';
cout << "Valeur retournée par la fonction : " << res << '\n';
return 0;
}
int changer(int* a) // définition: paramètre pointeur
{
*a += 4; // déréférencement
cout << "Val de a dans la fonction : " << *a << '\n';
return *a;
}Val de a dans la fonction : 9 Val de a Après l'exécution de la fonction : 9 Valeur retournée par la fonction : 9
Explication :
- On passe l'adresse de
a(&a) à la fonction. - Le paramètre
int* areçoit cette adresse (c'est une copie de l'adresse). - En déréférençant (
*a), on accède à la variable originale dansmain(). - La modification affecte directement la variable originale.
3. Passage par référence
Passage par référence
Une référence est un alias pour une autre variable. Vous pouvez spécifier un paramètre de fonction comme une référence, auquel cas la fonction utilise le mécanisme de passage par référence.
Fonctionnement :
- L'argument n'est pas copié.
- Le paramètre de référence est initialisé avec l'argument.
- Il devient un alias pour l'argument dans la fonction appelante.
- Toute utilisation du paramètre accède directement à l'argument original.
Syntaxe : On ajoute & après le nom du type.
Exemple 3 : Passage par référence
#include <iostream>
using namespace std;
int changer(int&); // prototype avec référence
int main(void){
int a {5}, res;
res = changer(a); // syntaxe identique au passage par valeur !
cout << "Val de a Après l'exécution de la fonction : " << a << '\n';
cout << "Valeur retournée par la fonction : " << res << '\n';
return 0;
}
int changer(int& a) // définition avec référence
{
a += 4; // pas de déréférencement nécessaire
cout << "Val de a dans la fonction : " << a << '\n';
return a;
}Val de a dans la fonction : 9 Val de a Après l'exécution de la fonction : 9 Valeur retournée par la fonction : 9
L'utilisation de références améliore les performances avec des objets tels que le type string. Le passage par valeur copierait l'objet, ce qui prendrait beaucoup de temps avec une longue chaîne et consommerait beaucoup de mémoire. Avec un paramètre de référence, il n'y a pas de copie.
Références vs Pointeurs
Les références sont similaires aux pointeurs. Pour voir cette similitude, prenons l'exemple d'une fonction qui augmente la valeur de la variable donnée avec deux fonctions : une qui accepte un pointeur et une qui accepte une référence.
Exemple 4 : Comparaison directe
#include <iostream>
using namespace std;
void changer_ptr(int *a){
*a += 5;
}
void changer_ref(int& a){
a += 5;
}
int main(void){
auto val{4};
changer_ptr(&val); // Passage par pointeur
cout << "Après le premier appel val = " << val << '\n';
changer_ref(val); // Passage par référence
cout << "Après le deuxième appel val = " << val << '\n';
return 0;
}Après le premier appel val = 9 Après le deuxième appel val = 14
Différences clés
| Caractéristique | Pointeur | Référence |
|---|---|---|
| Syntaxe d'appel | Nécessite & pour prendre l'adresse | Syntaxe identique au passage par valeur |
| Syntaxe dans la fonction | Nécessite * pour déréférencer | Utilisation directe (comme une variable normale) |
| Possibilité d'être nul | Oui (nullptr) | Non (doit toujours référencer quelque chose) |
| Test de nullité | Nécessaire avant utilisation | Pas nécessaire (sûr) |
| Compilation | Les compilateurs compilent généralement les références de la même manière que les pointeurs | |
- Référence : À privilégier quand on veut modifier l'argument et qu'il ne peut pas être nul (plus sûr, syntaxe plus propre).
- Pointeur : À utiliser quand l'argument peut être nul (permet de tester
nullptr). - Par valeur : Pour les petits types qu'on ne veut pas modifier, ou quand on a besoin d'une copie locale.
Le fait que la syntaxe d'appel pour une référence soit identique à celle du passage par valeur peut causer des surprises. En regardant l'appel changer(a), on ne peut pas savoir si a sera modifié ou non ! Il faut regarder le prototype de la fonction.
Récapitulatif des mécanismes
| Mécanisme | Modifie l'original ? | Copie ? | Syntaxe paramètre | Syntaxe appel |
|---|---|---|---|---|
| Passage par valeur | Non | Oui (copie) | type nom | fonction(var) |
| Passage par adresse | Oui | Oui (copie du pointeur) | type* nom | fonction(&var) |
| Passage par référence | Oui | Non (alias) | type& nom | fonction(var) |
- Le passage par valeur crée une copie : sécurité mais inefficace pour les gros objets.
- Le passage par adresse (pointeur) permet de modifier l'original via son adresse.
- Le passage par référence crée un alias : modifie l'original sans copie et sans syntaxe spéciale.
- Les références offrent une syntaxe plus élégante que les pointeurs.
- Les pointeurs peuvent être nuls (
nullptr), pas les références. - Le choix dépend du besoin : modification de l'original ?, présence possible de null ?, taille de l'objet ?
- Attention : la syntaxe d'appel des références est identique au passage par valeur (risque de confusion).
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.