Passage de paramètres en C++

22 Mar 2022 22 Mar 2022 3423 vues ESSADDOUKI Mostafa 9 min de lecture
Introduction et syntaxe de base
1 Introduction au langage C++ 2 Entrée-sortie en C++ - cin et cout 3 Inférence de type avec le mot-clé auto en C++ 4 Classe std::string et les chaînes de caractères en C++ 5 Les structures conditionnelles (if et switch) en C++ (C++17 et C++20) 6 Les boucles en C++ (C++17 et C++20) 7 La gestion des fichiers en C++
Pointeurs et fonctions
8 Introduction aux pointeurs en C++ - Déclaration et interêts 9 Les références en C++ - déclaration et interêts 10 Les tableaux en C++ - Déclaration et interêts 11 Introduction aux fonctions en C++ 12 Passer des arguments à une fonction en C++ 13 Déclarer un paramètre const en C++ 14 Les fonctions Lambda en C++ 15 Fonctions utiles (Mathématiques et caractères) en C++
Programmation OO
16 Classes et objets en C++ 17 Spécificateurs d'accès en C++ 18 Constructeurs et destructeur d'une classe en C++ 19 Fonctions membres en C++ 20 Membres statiques d'une classe en C++ 21 Fonctions en ligne en C++ - inline 22 Fonctions et classes amies en C++ - friend 23 Surcharge des fonctions en C++ 24 Surcharge des opérateurs en C++ 25 Héritage en C++ 26 La gestion d'exceptions en C++ : déclaration, utilisation et personnalisation 27 fonctions et classes templates en C++ 28 Les nouveautés C++20 pour améliorer les templates en C++
Structures de données
29 Introduction aux structures de données 30 Les structures en C++ et la différence avec les structures en C 31 Les listes chaînées en C++ 32 Les piles en C++ 33 File d'attente en C++ 34 Arbre binaire de recherche : définition et mise en oeuvre en C++
La bibliothèque standard (STL)
35 Introduction à la bibliothèque de Template Standard STL 36 Les itérateurs en C++ - définition, déclaration et exemples 37 La classe array en C++ (bibliothèque STL) <array> 38 La classe vector de la bibliothèque STL <vector> 39 La classe deque en C++ ( Bibliothèque STL) 40 La classe list en C++ (bibliothèque STL) <list> 41 La classe stack (Pile) en C++ (bibliothèque STL) <stack> 42 La classe queue (File d'attente) en C++ (bibliothèque STL) <queue> 43 La file d'attente prioritaire (classe priority_queue) - Bibliothèque STL 44 Les ensembles en C++ (Classe set <set> - Bibliothèque STL) 45 Les dictionnaires en C++ : Classe map (Bibliothèque STL) 46 Introduction aux algorithmes de la bibliothèque STL (programmation compétitive) 47 Tri et méthodes associées en C++ - Bibliothèque STL 48 Recherche dichotomique et méthodes associées en C++ - Bibliothèque STL 49 Appliquer un prédicat ou une fonction aux éléments d'une séquence en C++ - Bibliothèque STL 50 Recherche dans une séquence et méthodes associées en C++ - Bibliothèque STL

Passage de paramètres en C++

Importance

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.

Conversions implicites

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.

Attention aux pertes de précision

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

 Mécanisme

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

   
Fonction changer() par valeur C++
#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;
}
Sortie
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 a dans main() 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).
Conclusion sur le passage par valeur

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)

 Mécanisme

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)

   
Fonction changer() par pointeur C++
#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;
}
Sortie
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* a reçoit cette adresse (c'est une copie de l'adresse).
  • En déréférençant (*a), on accède à la variable originale dans main().
  • La modification affecte directement la variable originale.

3. Passage par référence

 Mécanisme

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

   
Fonction changer() par référence C++
#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;
}
Sortie
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
Performance avec les gros objets

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

   
Pointeur vs Référence C++
#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;
}
Sortie
Après le premier appel val = 9
Après le deuxième appel val = 14

Différences clés

CaractéristiquePointeurRéférence
Syntaxe d'appelNécessite & pour prendre l'adresseSyntaxe identique au passage par valeur
Syntaxe dans la fonctionNécessite * pour déréférencerUtilisation directe (comme une variable normale)
Possibilité d'être nulOui (nullptr)Non (doit toujours référencer quelque chose)
Test de nullitéNécessaire avant utilisationPas nécessaire (sûr)
CompilationLes compilateurs compilent généralement les références de la même manière que les pointeurs
Quand utiliser quoi ?
  • 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.
Piège potentiel des références

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écanismeModifie l'original ?Copie ?Syntaxe paramètreSyntaxe appel
Passage par valeurNonOui (copie)type nomfonction(var)
Passage par adresseOuiOui (copie du pointeur)type* nomfonction(&var)
Passage par référenceOuiNon (alias)type& nomfonction(var)
Points clés à retenir
  • 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.