Résumé
Cette leçon étudie par quels chemins les données entrent dans une fonction et ses résultats en sortent. Par défaut, C++ passe les arguments par valeur : le paramètre est une copie, et rien de ce que la fonction lui fait n’atteint l’original. Une référence, en revanche, est un second nom pour une case qui existe déjà, et elle permet à la fonction de modifier la variable de l’appelant ; la référence constante permet de lire une donnée volumineuse sans la copier et sans pouvoir l’altérer. Pour renvoyer plusieurs résultats, la bibliothèque offre std::pair et std::tuple, que l’on reçoit avec des liaisons structurées. Suivent la portée d’un nom (où il est visible) et la durée de stockage d’une variable (quand elle existe), avec le masquage d’un nom et les variables locales static, et la leçon se clôt sur la référence qui survit à sa variable. Le concept qui unifie le tout est le flux de données entre les parties d’un programme : copier ou partager, et la visibilité des noms comme mécanisme d’isolement.
Objectifs d’apprentissage
- Prédire l’effet d’une fonction sur ses arguments selon qu’ils sont passés par valeur ou par référence.
- Choisir le mode de passage de chaque paramètre (valeur pour les petits types, référence constante pour les données volumineuses, référence pour ce que l’on modifie) et justifier ce choix.
- Renvoyer plusieurs résultats avec
std::pairoustd::tupleet les recevoir avec des liaisons structurées. - Déterminer la portée et la durée de stockage de chaque variable d’un programme, y compris les variables locales
staticet celles qu’une autre masque. - Reconnaître la référence à une variable locale ou à une valeur temporaire qui n’existe plus, avec l’avertissement du compilateur qui l’annonce, et la corriger.
Passage par valeur
Le paramètre est une copie
La leçon Fonctions a montré que chaque appel crée un cadre sur la pile, avec les paramètres et les variables locales de la fonction, et que le cadre disparaît au retour. Il reste à préciser quelle relation un paramètre entretient avec l’argument qui l’initialise. La réponse par défaut de C++ est la plus simple : le paramètre est une nouvelle variable locale, initialisée avec une copie de la valeur de l’argument. S’il en est ainsi, une fonction qui modifie ses paramètres modifie ses copies, et les variables de l’appelant ne changent pas. Le programme suivant le met à l’épreuve avec une fonction qui tente d’échanger deux entiers.
#include <print>// Tente d’échanger a et b ; n’échange que leurs copies.void swapValues(int a, int b){ const int saved{a}; a = b; b = saved; std::println("Dedans : a = {}, b = {}", a, b);}int main(){ int x{1}; int y{2}; swapValues(x, y); std::println("Après : x = {}, y = {}", x, y);}Dedans : a = 2, b = 1Après : x = 1, y = 2Dans la fonction, l’échange se fait correctement ; après l’appel, x et y conservent leurs valeurs. Le compilateur n’avertit pas, car il n’y a rien à signaler : le code échange deux variables locales qui disparaissent à la fin. La figure suit les cadres de la pile pendant l’appel.
Le cadre de main contient x et y ; l’appel crée au-dessus le cadre de swapValues, avec ses propres cases.
Démonstration
Avant l’appel, le cadre de
maina deux cases :xavec 1 etyavec 2.L’appel crée le cadre de
swapValues. Ses paramètresaetbsont des cases nouvelles, et on y copie les valeurs des arguments : 1 et 2. À partir de ce moment, il ne reste aucun lien entreaetx, si ce n’est qu’ils ont commencé avec la même valeur.La variable locale
savedest créée dans le même cadre et garde une copie dea.Les deux affectations échangent
aetb. Les cases qui changent sont toutes dans le cadre du haut ; celles demainne sont pas touchées.Au retour, le cadre de
swapValuesdisparaît tout entier, avec l’échange qu’il contenait.xetyvalent toujours 1 et 2.
Ce que la copie protège
Le passage par valeur n’est pas un défaut, mais la règle qui rend les fonctions raisonnables. Premièrement, l’argument peut être n’importe quelle expression, pas seulement une variable : swapValues(x + 1, 7) est valide, car le paramètre s’initialise avec une valeur, d’où qu’elle vienne. Deuxièmement, l’appelant sait par la déclaration, sans lire le corps, que ses variables ne changeront pas. Troisièmement, dans la fonction, le paramètre est une variable locale de plus, qui peut servir de variable de travail sans conséquences à l’extérieur.
#include <print>// Nombre de chiffres décimaux de n.// Précondition : n >= 0.// Postcondition : renvoie le nombre de chiffres de n (1 pour 0).int digitCount(int n){ int count{1}; while (n >= 10) { n = n / 10; ++count; } return count;}int main(){ const int population{48'210}; std::println("{} a {} chiffres", population, digitCount(population));}48210 a 5 chiffresLa fonction détruit son paramètre en le divisant par 10 jusqu’à ce qu’il n’ait plus qu’un chiffre ; comme c’est une copie, population reste intacte, et elle peut être const. Il en découle une règle de lecture valable pour tout le cours : si un paramètre ne porte pas le signe & que l’on étudie dans la section suivante, la fonction ne peut pas modifier la variable de l’appelant, quoi qu’elle fasse de ce paramètre.
Chargement du contenu…