Paramètres et arguments
Les paramètres d'une fonction sont les variables nommées de sa définition ; les arguments sont les valeurs réelles que vous lui transmettez lors de l'appel. La page précédente a montré comment définir et appeler des fonctions ; cette page traite de la façon dont ces valeurs entrent réellement, car C++ vous offre plusieurs moyens et le choix affecte à la fois la correction et la vitesse.
Par défaut en C++, c'est le passage par valeur : la fonction reçoit une copie.
À l'intérieur de addTen, n est une variable distincte initialisée à partir de score. Réaffecter n ne touche que cette copie, donc score reste intact de retour dans main. C'est sûr et prévisible - la fonction ne peut pas écraser vos données par accident - et c'est exactement pourquoi c'est le comportement par défaut.
Passage par référence : laisser une fonction modifier l'appelant
Parfois, vous voulez que la fonction modifie la variable de l'appelant. Ajoutez un & au type du paramètre et il devient une référence - un alias de l'original, pas une copie :
La seule différence avec le premier exemple est le &, mais désormais n et score sont le même objet. C'est la manière standard de « renvoyer » plus d'une valeur ou de mettre à jour quelque chose sur place. Un usage classique est l'échange de deux variables :
Sans le &, swapValues brasserait deux copies et main ne verrait aucun changement - un bug de débutant très courant.
Références const : un accès peu coûteux, en lecture seule
Le passage par valeur copie l'argument. Pour un int, cela ne coûte rien, mais copier un gros string ou vector à chaque appel représente un travail réel et gaspillé. La solution est une référence const (const T&) : vous obtenez la vitesse de la référence (aucune copie) plus une promesse, imposée par le compilateur, de ne pas modifier l'argument.
Une règle pratique utile : passez les petits types intégrés (int, double, char, bool, pointeurs) par valeur, et passez les gros objets que vous n'avez besoin que de lire par référence const. Réservez une T& non const simple aux cas où vous avez réellement l'intention de modifier l'objet de l'appelant.
Un piège subtil : une int& n simple ne peut pas se lier à un temporaire ou à un littéral. addTen(5) du premier exemple ne compilerait pas si le paramètre était int&, car 5 n'est pas une variable à laquelle vous pouvez créer un alias. Une const int& peut se lier à 5, ce qui est une raison de plus pour laquelle les références const sont si largement utilisées.
Arguments par défaut
Vous pouvez donner à un paramètre une valeur de repli afin que les appelants puissent l'omettre. Si l'argument manque, la valeur par défaut est utilisée :
Deux règles font trébucher les gens. Premièrement, les valeurs par défaut doivent être à la fin - dès qu'un paramètre a une valeur par défaut, tous ceux qui le suivent doivent en avoir une aussi. Vous ne pouvez pas écrire void f(int a = 1, int b) car il n'y aurait aucun moyen de fournir b en sautant a. Deuxièmement, lorsqu'une fonction est déclarée dans un en-tête et définie ailleurs, mettez la valeur par défaut uniquement dans la déclaration, ne la répétez jamais dans la définition - la répéter est une erreur de compilation.
Passer des tableaux et des vecteurs
Un tableau brut se dégrade en pointeur lorsqu'il est passé, donc la fonction perd la trace de sa taille - vous passez presque toujours la longueur à côté :
Comme le tableau est devenu un pointeur, sizeof(arr) à l'intérieur de sum donnerait la taille d'un pointeur, pas du tableau - un bug notoire. En C++ moderne, préférez un std::vector (ou un std::span en C++20), passé par référence const, qui transporte sa propre taille :
Notez le const& : retirez-le et chaque appel copie le vecteur entier. Pour un vecteur de quatre éléments, c'est inoffensif, mais pour un million d'éléments, c'est un gouffre de performance silencieux.
Paramètres de type pointeur
Vous pouvez aussi passer un pointeur (T*). Comme une référence, cela permet à la fonction d'atteindre les données de l'appelant, mais un pointeur peut être réaffecté ou être nul - c'est donc le bon outil lorsque « aucune valeur » est une option légitime :
L'appelant passe &value pour partager son adresse, et la fonction écrit à travers *out. La différence clé avec les références : un pointeur peut être nullptr, donc une fonction qui en reçoit un devrait vérifier avant de le déréférencer - sauter cette protection et déréférencer un pointeur nul est un comportement indéfini, généralement un plantage. Si « aucune valeur » n'a jamais de sens, une référence est plus propre car elle ne peut tout simplement pas être nulle.
Suite : Les références
Les paramètres sont l'endroit où les références gagnent leur place, mais les références sont une fonctionnalité à part entière - des alias que vous pouvez créer pour n'importe quelle variable, pas seulement à l'intérieur d'une signature de fonction. La page suivante explore en profondeur le fonctionnement des références par elles-mêmes : comment les déclarer, pourquoi elles doivent être initialisées immédiatement, la différence entre une référence lvalue et une référence const, et les façons subtiles dont une référence peut finir par pendre (dangling).
Questions fréquentes
Quelle est la différence entre passage par valeur et passage par référence en C++ ?
Le passage par valeur copie l'argument dans le paramètre, donc les modifications à l'intérieur de la fonction n'affectent pas l'appelant. Le passage par référence (int&) fait du paramètre un alias de la variable de l'appelant, donc les modifications sont visibles à l'extérieur. Utilisez void f(int x) pour copier et void f(int& x) pour modifier l'original.
Quand faut-il utiliser un paramètre de référence const en C++ ?
Utilisez const T& lorsque vous voulez lire un gros objet sans le copier et sans laisser la fonction le modifier - par exemple void print(const string& s). Cela vous donne la vitesse du passage par référence avec la sécurité du passage par valeur. Pour les petits types comme int ou char, le passage par valeur simple est tout aussi rapide.
Que sont les arguments par défaut en C++ ?
Les arguments par défaut permettent à un paramètre de prendre une valeur de repli lorsque l'appelant l'omet, par ex. void greet(string name = "there"). Les valeurs par défaut doivent être les paramètres finaux (les plus à droite), et vous les spécifiez uniquement dans la déclaration, pas dans la définition si les deux sont séparées.