programmez-en-d/parametres_de_fonctions.whata

731 lines
27 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[set
title = "Les paramètres de fonction"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
Ce chapitre couvre les différentes manières de définir des paramètres de fonction.
Certaines idées de ce chapitre sont déjà apparues dans les chapitres précédents. Par exemple, le mot-clé [c ref] que nous avons vu dans le [[part:foreach | chapitre sur les boucles [c foreach]]] permettait d'accéder directement aux éléments eux-mêmes dans les boucles [c foreach] au lieu d'accéder à des copies de ces éléments.
De plus, nous avons couvert les mots-clés [c const] et [c immutable] dans le chapitre précédent.
Nous avons écrit des fonctions qui produisaient des résultats en utilisant leurs paramètres. Par exemple, la fonction suivante utilise ses paramètres dans un calcul~ :
[code=d <<<
double moyennePondérée(double noteDuQuiz, double noteFinale)
{
return noteDuQuiz * 0.4 + noteFinale * 0.6;
}
>>>]
Cette fonction calcule la note moyenne en prenant 40% de la note du quiz et 60% de la note finale. Voici comment elle peut être utilisée :
[code=d <<<
int noteDuQuiz = 76;
int noteFinale = 80;
writefln("Moyenne pondérée : %2.0f",
moyennePondérée(noteDuQuiz, noteFinale));
>>>]
[ = La plupart des paramètres sont copiés
Dans le code qui précède, les deux variables sont passées en arguments à [c moyennePondérée()] et la fonction utilise ses paramètres. Ceci peut donner la mauvaise impression que la fonction utilise directement les variables qui sont passées en argument. En réalité, la fonction [* utilise des copies] de ces variables.
Cette distinction est importante parce que modifier un paramètre change uniquement la copie. On peut observer cela dans la fonction suivante qui essaie de modifier ses paramètres (c.-à-d. avoir un effet de bord). Supposons que la fonction suivante soit écrite pour réduire l'énergie d'un personnage de jeu~ :
[code=d <<<
void reduireEnergie(double energie)
{
energie /= 4;
}
>>>]
Voici un programme qui teste [c reduireEnergie()]~ :
[code=d <<<
import std.stdio;
void reduireEnergie(double energie)
{
energie /= 4;
}
void main()
{
double energie = 100;
reduireEnergie(energie);
writeln("Nouvelle energie : ", energie);
}
>>>]
La sortie~ :
[output <<<
Nouvelle energie : 100 ← Non changée !
>>>]
Même si [c reduireEnergie()] divise la valeur de son paramètre par 4, la variable [c energie] dans [c main()] ne change pas. La raison à ceci est que la variable [c energie] dans [c main()] et le paramètre [c energie] de [c reduireEnergie()] sont distincts~ ; le paramètre est une copie de la variable de [c main()].
Pour observer cela plus précisément, plaçons quelques [c writeln()]~ :
[code=d <<<
import std.stdio;
void reduireEnergie(double energie)
{
writeln("En entrant dans la fonction : ", energie);
energie /= 4;
writeln("En sortant de la fonction : ", energie);
}
void main()
{
double energie = 100;
writeln("En appelant la fonction : ", energie);
reduireEnergie(energie);
writeln("Après l'appel de la fonction : ", energie);
}
>>>]
La sortie :
[output <<<
En appelant la fonction : 100
En entrant dans la fonction : 100
En sortant de la fonction : 25 ← Le paramètre change,
Après l'appel de la fonction : 100 ← la variable reste la même
>>>]
]
[ = Les objets de type référence ne sont pas copiés
Les éléments des tranches et des tableaux associatifs, ainsi que les objets de classes, ne sont pas copiés quand ils sont passés en paramètres. De telles variables sont passés aux fonctions par références. En effet, le paramètre devient une référence à l'objet~ ; les modifications apportées à travers la référence affectent l'objet.
Étant des tranches, les chaînes sont également passées par référence~ :
[code=d <<<
import std.stdio;
void premiereLettreEnPoint(dchar[] chaine)
{
chaine[0] = '.';
}
void main()
{
dchar[] chaine = "abc"d.dup;
premiereLettreEnPoint(chaine);
writeln(chaine);
}
>>>]
La modification apportée au premier élément du paramètre affecte l'élément dans [c main()]~ :
[output <<<
.bc
>>>]
]
[ = Les qualificatifs de paramètre
Les paramètres sont passés aux fonctions selon les règles générales suivantes~ :
- les types valeur sont copiés~ ;
- les types référence sont passés par références.
Ce sont les règles par défaut qui sont appliquées quand les définitions de paramètres n'ont pas de qualificatifs. Les qualificatifs suivant changent la manière dont les paramètres sont passés et quelles opérations sur eux sont permises.
[ = [c in]
Nous avons vu que les fonctions sont un service qui produit des valeurs et peut avoir des effets de bords. Le mot-clé [c in] indique qu'un paramètre va être utilisé seulement comme une donnée d'entrée. De tels paramètres ne peuvent pas être modifiés par la fonction. [c in] implique [c const]~ :
[code=d <<<
import std.stdio;
double poidsTotal(in double totalActuel,
in double poids,
in double quantitéAjoutée)
{
return totalActuel + (poids * quantitéAjoutée);
}
void main()
{
writeln(poidsTotal(1.23, 4.56, 7.89));
}
>>>]
Les paramètres [c in] ne peuvent pas être modifiés~ :
[code=d <<<
void foo(in int valeur)
{
valeur = 1; // ← ERREUR de compilation
}
>>>]
]
[ = [c out]
Nous avons vu que les fonction retournent les valeurs qu'elles produisent comme leur valeur de retour. Parfois, n'avoir qu'une seule valeur de retour est limitatif et certaines fonctions peuvent avoir besoin de produire plus d'une valeur. (Note~ : il est en fait possible de retourner plus d'un résultat en définissant le type de retour comme un n-upplet ou une structure. Nous verrons ces fonctionnalités dans des chapitres ultérieurs).
Le mot-clé [c out] permet aux fonctions de retourner des résultats à travers leurs paramètres. Quand les paramètres [c out] sont modifiés dans une fonction, ces modifications affectent la variable qui a été passée à la fonction.
Examinons la fonction qui divise deux nombres et produit le quotient et le reste. La valeur de retour peut être utilisée pour le quotient et le reste peut être retournée à travers un paramètre [c out]~ :
[code=d <<<
import std.stdio;
int diviser(in int dividende, in int diviseur, out int reste)
{
reste = dividende % diviseur;
return dividende / diviseur;
}
void main()
{
int reste;
int resultat = diviser(7, 3, reste);
writeln("résultat : ", resultat, ", reste : ", reste);
}
>>>]
Modifier le paramètre [c reste] de la fonction modifie la variable [c reste] dans [c main()] (leurs noms n'ont pas besoin d'être les mêmes)~ :
[output <<<
résultat: 2, reste : 1
>>>]
Indépendamment de leurs valeurs au moment de l'appel, la valeur [c init] du type des paramètres [c out] leur est automatiquement affectée~:
[code=d <<<
import std.stdio;
void foo(out int parametre)
{
writeln("Après être entré dans la fonction : ", parametre);
}
void main()
{
int variable = 100;
writeln("Avant l'appel de la fonction : ", variable);
foo(variable);
writeln("Après le retour de la fonction : ", variable);
}
>>>]
Même s'il n'y a pas d'affectation explicite au paramètre dans la fonction, la valeur du paramètre devient automatiquement la valeur initiale de [c int], affectant la variable dans [c main()]~ :
[output <<<
Avant l'appel de la fonction : 100
Après être entré dans la fonction : 0 ← La valeur de int.init
Après le retour de la fonction : 0
>>>]
Cela montre que les paramètres [c out] ne peuvent pas passer de valeur aux fonctions~ ; il sont strictement là pour transmettre des valeurs hors de la fonction.
Nous verrons dans des chapitres ultérieurs que retourner des n-upplets ou structures peut être mieux qu'utiliser des paramètres [c out].
]
[ = [c const]
Comme nous l'avons vu dans le chapitre précédent, [c const] garantit que le paramètre ne sera pas modifié dans la fonction. Il est utile aux programmeurs de savoir que certaines variables ne seront pas modifiées par la fonction. [c const] rend également les fonction plus utiles en autorisant des variables [c const], [c immutable] et non [c immutable] à être passées en paramètres~ :
[code=d <<<
import std.stdio;
dchar derniereLettre(const dchar[] str)
{
return str[$ - 1];
}
void main()
{
writeln(derniereLettre("constante"));
}
>>>]
]
[ = [c immutable]
Comme nous l'avons vu dans le chapitre précédent, [c immutable] force certains arguments à être des éléments [c immutable]. À cause d'un tel prérequis, la fonction suivante ne peut être appelée qu'avec des chaînes immuables (par ex. avec des littéraux de chaînes)~ :
[code=d <<<
import std.stdio;
dchar[] mix(immutable dchar[] premier,
immutable dchar[] second)
{
dchar[] resultat;
int i;
for (i = 0; (i < premier.length) && (i < second.length); ++i) {
resultat ~= premier[i];
resultat ~= second[i];
}
resultat ~= premier[i..$];
resultat ~= second[i..$];
return resultat;
}
void main()
{
writeln(mix("BONJOUR", "le monde"));
}
>>>]
Comme il demande un prérequis sur le paramètre, le qualificatif [c immutable] ne devrait être utilisé que quand il est vraiment nécessaire. Étant plus accueillant, [c const] est plus utile.
]
[ = [c ref]
Ce mot-clé permet de passer un paramètre par référence même dans les cas où il serait normalement passé par valeur.
Pour que la fonction [c reduireEnergie()] vue plus tôt modifie la variable qui lui est passée en argument, son paramètre doit être marqué avec [c ref]~ :
[code=d <<<
import std.stdio;
void reduireEnergie(ref double energie)
{
energie /= 4;
}
void main()
{
double energie = 100;
reduireEnergie(energie);
writeln("Nouvelle energie : ", energie);
}
>>>]
Cette fois, les modifications qui sont appliquées aux paramètres affectent la variable qui est passée à la fonction dans [c main()]~ :
[output <<<
Nouvelle energie: 25
>>>]
Comme on peut le remarquer, les paramètres [c ref] peuvent être utilisés aussi bien comme entrée que comme sortie. Les paramètres [c ref] peuvent aussi être vus comme des [* alias] des variables passées en argument. Le paramètre de la fonction [c energie] ci-dessus est un alias de la variable [c energie] dans [c main()].
Tout comme les paramètres [c out], les paramètres [c ref] permettent aux fonctions d'avoir des effets de bords. En fait, [c reduireEnergie()] ne retourne pas de valeur~ ; elle ne fait qu'avoir un effet de bord à travers son seul paramètre.
Le style de programmation dit fonctionnel favorise les valeurs de retour sur les effets de bords, à tel point que certains langages de programmations fonctionnels ne permettent pas du tout les effets de bords. Les fonctions qui produisent des résultat uniquement par leur valeur de retour sont en effet plus faciles à comprendre, à écrire correctement et à maintenir.
La même fonction peut être écrite en style fonctionnel en retournant le résultat, au lieu de créer un effet de bord.
[code=d <<<
import std.stdio;
double energieReduite(double energie)
{
return energie / 4;
}
void main()
{
double energie = 100;
energie = energieReduite(energie);
writeln("Nouvelle energie: ", energie);
}
>>>]
Notez également le changement de nom de la fonction. Il ne s'agit plus d'un verbe, mais d'un groupe nominal.
]
[ = [c auto ref]
Ce qualificateur ne peut être utilisé qu'avec les modèles (''templates''). Comme nous le verrons dans le chapitre suivant, un paramètre [c auto ref] prend les valeurs de gauche par référence et les valeurs de droite par copie.
]
[ = [c inout]
Malgré son nom composé de [c in] et de [c out], ce mot-clé ne veut pas dire [* entrée et sortie] ; nous avons déjà vu que le mot-clé qui fait cela est [c ref].
[c inout] transmet la mutabilité du paramètre au type de retour. Si le paramètre est mutable, [c const] ou [c immutable], alors la valeur de retour est respectivement mutable, [c const] ou [c immutable].
Pour voir l'utilité d'[c inout], examinons une fonction qui retourne une tranche qui a un élément de moins au début et à la fin que la tranche d'origine~ :
[code=d <<<
import std.stdio;
int[] rognée(int[] tranche)
{
if (tranche.length) {
--tranche.length; // rogner depuis la fin
if (tranche.length) {
tranche = tranche[1 .. $]; // rogner depuis le début
}
}
return tranche;
}
void main()
{
int[] nombres = [ 5, 6, 7, 8, 9 ];
writeln(rognée(nombres));
}
>>>]
La sortie :
[code=d <<<
[6, 7, 8]
>>>]
Selon le chapitre précédent, pour que la fonction soit plus utile, son paramètre devrait être [c const(int)~[~]] parce que le paramètre n'est pas modifié dans la fonction. (Notez qu'il n'est pas dangereux de modifier le paramètre [c tranche] lui-même parce c'est une copie de l'argument originel.)
Cependant, définir la fonction de la façon suivante entraînerait une erreur de compilation~ :
[code=d <<<
int[] rognée(const(int)[] tranche)
{
// ...
return tranche; // ← ERREUR de compilation
}
>>>]
L'erreur de compilation indique que la tranche de [c const(int)] ne peut pas être retournée comme une tranche de [c mutable int]~ :
[output <<<
Error: cannot implicitly convert expression (tranche) of type
const(int)[] to int[]
>>>]
On pourrait croire que spéficier le type de retour comme [c const(int)~[~]] peut être la solution~ :
[code=d <<<
const(int)[] rognée(const(int)[] tranche)
{
// ...
return tranche; // compile maintenant
}
>>>]
Bien que le code puisse maintenant être compilé, il apporte une limitation~ : même si la fonction est appelée avec une tranche d'éléments [c mutable], la tranche retournée contient des elements [c const]. Pour voir à quel point ceci est limitant, regardons le code suivant, qui essaie de modifier les éléments d'une tranche autres que ceux qui sont au debut ou à la fin~ :
[code=d <<<
int[] nombres = [ 5, 6, 7, 8, 9 ];
int[] milieu = rognée(nombres); // ← ERREUR de compilation
milieu[] *= 10;
>>>]
Comme on peut s'y attendre, la tranche retournée de type [c <<< const(int)[]>>>] ne peut pas être assignée à une tranche de type [c <<< int[]>>>]~ :
[code=d <<<
Error: cannot implicitly convert expression (rognée(nombres))
of type const(int)[] to int[]
>>>]
Cependant, comme la tranche de départ est constitué d'élément mutable, cette limitation peut être vue comme artificielle et malheureuse. [c inout] résout ce problème de mutabilité entre les paramètres et les valeurs de retour. Il est indiqué aussi bien sur le type du paramètre que sur le type de retour et transmet la mutabilité du premier au second~ :
[code=d <<<
inout(int)[] rognée(inout(int)[] tranche)
{
// ...
return tranche;
}
>>>]
Avec ce changement, la même fonction peut maintenant être appelée avec des tranches mutable, [c const] et [c immutable]~ :
[code=d <<<
{
int[] nombres = [ 5, 6, 7, 8, 9 ];
// Le type de retour est une tranche d'élements mutables
int[] milieu = rognée(nombres);
milieu[] *= 10;
writeln(milieu);
}
>>>]
[code=d <<<
{
immutable int[] nombres = [ 10, 11, 12 ];
// Le type de retour est une tranche d'éléments immuables
immutable int[] milieu = rognée(nombres);
writeln(milieu);
}
>>>]
[code=d <<<
{
const int[] nombres = [ 13, 14, 15, 16 ];
// Le type de retour est une tranche d'éléments const
const int[] milieu = rognée(nombres);
writeln(milieu);
}
>>>]
]
[ = [c lazy] (paresseux)
Il est naturel de s'attendre à ce que les arguments soient évalués avant d'entrer dans les fonctions qui utilisent ces arguments. Par exemple, la fonction [c ajouter()] ci-dessous est appelée avec les valeurs de retours de deux autres fonctions~ :
[code=d <<<
resultat = ajouter(uneQuantité(), uneAutreQuantité());
>>>]
Pour qu'[c ajouter()] soit appelée, [c uneQuantité()] et [c uneAutreQuantité()] doivent être appelée avant. Autrement, les valeurs dont [c ajouter()] a besoin ne seraient pas disponibles.
Évaluer les arguments avant d'appeler une fonction est ''non paresseux'' (''eager'').
Cependant, certains paramètres peuvent ne pas être utilisés du tout par une fonction selon certaines conditions. Dans de tels cas, les évaluations non paresseuses des arguments sont inutiles.
Regardons un programme qui utilise un de ses paramètres seulement quand il est nécessaire. La fonction suivante essaie de prendre le nombre requis d'œufs dans le réfrigérateur. Quand il y a un nombre suffisant d'œufs dans le réfrigérateur, elle n'a pas besoin de savoir combien d'œufs les voisins ont~ :
[code=d <<<
void faireOmelette(in int œufsRequis,
in int œufsDansLeRefrigérateur,
in int œufsDesVoisins)
{
writefln("Besoin de faire une omelette de %s œufs", œufsRequis);
if (œufsRequis <= œufsDansLeRefrigérateur) {
writeln("Prendre tous les œufs dans le réfrigérateur");
} else if (œufsRequis <= (œufsDansLeRefrigérateur + œufsDesVoisins)) {
writefln("Prendre %s œufs du réfrigérateur"
" et %s œufs chez les voisins",
œufsDansLeRefrigérateur, œufsRequis - œufsDansLeRefrigérateur);
} else {
writefln("Impossible de faire une omelette de % œufs", œufsRequis);
}
}
>>>]
De plus, supposons qu'il y a une fonction qui calcule et retourne le nombre total d'œufs du voisinage. Pour des raisons pédagogiques, la fonction affiche aussi quelques informations~ :
[code=d <<<
int nombreŒufs(in int[string] œufsDisponibles)
{
int resultat;
foreach (voisin, nombre; œufsDisponibles) {
writeln(voisin, " : ", nombre, " œufs");
resultat += nombre;
}
writefln("Un total de %s œufs disponibles chez les voisins",
resultat);
return resultat;
}
>>>]
La fonction itère sur les élements d'un tableau associatif et somme le nombre d'œufs.
La fonction [c faireOmelette()] peut être appelée avec la valeur de retour de [c nombreŒufs()] comme dans le programme suivant~ :
[code=d <<<
import std.stdio;
void main()
{
int[string] chezLesVoisins = [ "Jane":5, "Jim":3, "Bill":7 ];
faireOmelette(2, 5, nombreŒufs(chezLesVoisins));
}
>>>]
Comme on peut le constater dans la sortie du programme, la fonction [c nombreŒufs()] est d'abord exécutée et [c faireOmelette()] est ensuite appelée~ :
[output <<<
Jane : 5 œufs ⎫
Bill : 7 œufs ⎬ Comptage des œufs chez les voisins
Jim : 3 œufs ⎭
Un total de 15 œufs disponible chez les voisins
Besoin de faire une omelette de 2 œufs
Prendre tous les œufs depuis le réfrigérateur
>>>]
Bien qu'il soit possible de faire l'omelette de deux œufs avec les œufs du réfrigérateur uniquement, les œufs chez les voisins ont été comptés de façon non paresseuse.
Le mot-clé [c lazy] («~ paresseux~ ») indique qu'une expression qui a été passée à une fonction comme paramètre sera évaluée ''seulement si'' et ''quand'' elle est nécessaire~ :
[code=d <<<
void faireOmelette(in int œufsRequis,
in int œufsDansLeRefrigérateur,
lazy int œufsDesVoisins)
{
// … Le corps de la fonction est le même qu'avant ...
}
>>>]
Comme vu dans la nouvelle sortie, quand le nombre d'œufs dans le réfrigérateur satisfait le nombre d'œufs requis, le comptage des œufs chez les voisins ne se fait plus~ :
[output <<<
Besoin de faire une omelette de 2 œufs
Prendre tous les œufs dans le réfrigérateur
>>>]
Ce comptage sera toujours fait si nécessaire. Par exemple, considérons le cas où le nombre d'œufs requis est plus grand que le nombre d'œufs dans le réfrigérateur~ :
[code=d <<<
faireOmelette(9, 5, nombreŒufs(chezLesVoisins));
>>>]
Cette fois, le nombre total d'œufs chez les voisins est vraiment nécessaire~ :
[code=d <<<
Besoin de faire une omelette de 9 œufs.
Jane : 5 œufs
Bill : 7 œufs
Jim : 3 œufs
Un total de 15 œufs disponible chez les voisins
Prendre 5 œufs dans le réfrigérateur et 4 œufs chez les voisins
>>>]
Les valeurs des paramètres [c lazy] sont évalués à chaque fois qu'ils sont utilisés dans la fonction.
Par exemple, parce que le paramètre [c lazy] de la fonction suivante est utilisé trois fois dans la fonction, l'expression qui donne sa valeur est évaluée trois fois~ :
[code=d <<<
import std.stdio;
int ValeurDeLArgument()
{
writeln("Calcul...");
return 1;
}
void FonctionAvecParametreLazy(lazy int valeur)
{
int resultat = valeur + valeur + valeur;
writeln(resultat);
}
void main()
{
FonctionAvecParametreLazy(ValeurDeLArgument());
}
>>>]
La sortie :
[output <<<
Calcul...
Calcul...
Calcul...
3
>>>]
]
[ = [c scope]
Ce mot-clé indique qu'un paramètre ne sera pas utilisé au delà de la portée de la fonction~ :
[code=d <<<
int[] trancheGlobale;
int[] foo(scope int[] parametre)
{
trancheGlobale = parametre; // ← ERREUR de compilation
return parametre; // ← ERREUR de compilation
}
void main()
{
int[] tranche = [ 10, 20 ];
int[] resultat = foo(tranche);
}
>>>]
La fonction casse la promesse de [c scope] à deux endroits~ : elle assigne le paramètre à une variable globale et le retourne. Ces deux actions rendraient possible l'accès aux paramètres après que la fonction se soit terminée.
(Note~ : dmd 2.066.1, le compileur qui a été utilisé pour compiler les exemples de ce chapitre, ne prend pas en charge le mot-clé [c scope]. )
]
[ = [c shared]
Ce mot-clé nécessite que le paramètre soit partageable entre les fils d'exécutions~ :
[code=d <<<
void foo(shared int[] i)
{
// ...
}
void main()
{
int[] nombres = [ 10, 20 ];
foo(nombres); // ← ERREUR de compilation
}
>>>]
Le programme ci-avant ne peut pas être compilé parce que l'argument n'est pas partagé. Le programme peut être compilé avec les changements suivants~ :
[code=d <<<
shared int[] nombres = [ 10, 20 ];
foo(nombres); // maintenant, compile
>>>]
Nous utiliserons le mot-clé [c shared] dans le [[part:concurrence | chapitre sur la concurrence des partages de données]].
]
]
[ = Résumé
- Le paramètre est ce que la fonction prend depuis le code qui l'appelle pour réaliser une tâche.
- L'argument est une expression (par exemple une variable) qui est passée à une fonction en paramètre.
- Les arguments de type valeur sont copiés lors du passage, les arguments de type référence sont passés par référence (nous reverrons ce sujet dans des chapitres ultérieurs).
- [c in] indique que le paramètre est seulement pour une entrée de données.
- [c out] indique que le paramètre est seulement pour une sortie de données.
- [c ref] indique que le paramètre est pour une entrée et une sortie de données.
- [c auto ref] est seulement pour les modèles. Cela spécifie qu'un argument de type «~ valeur de gauche~ » argument est passé par référence et qu'un argument de type «~ valeur de droite~ » est passé par copie.
- [c const] garantit que le paramètre n'est pas modifié à l'intérieur de la fonction.
- [c immutable] nécessite que l'argument soit immuable.
- [c inout] apparaît aussi bien pour le paramètre que pour le type de retour et transfère la mutabilité du paramètre au type de retour.
- [c lazy] évalue le paramètre quand (et à chaque fois que) sa valeur est utilisée.
- [c scope] garantit qu'aucune référence au paramètre ne sera «~ fuitée~ » par la fonction.
- [c shared] nécessite que le paramètre soit partagé.
]
[ = Exercice
Le programme suivant essaie d'échanger les valeurs de deux arguments~ :
[code=d <<<
import std.stdio;
void echanger(int premier, int second)
{
int temp = premier;
premier = second;
second = temp;
}
void main()
{
int a = 1;
int b = 2;
echanger(a, b);
writeln(a, ' ', b);
}
>>>]
Le programme n'a aucun effet sur [c a] ni sur [c b]~ :
[output <<<
1 2 ← non échangés
>>>]
Corrigez la fonction pour que les valeurs de [c a] et [c b] soient échangées.
[[part:corrections/parametres_de_fonctions | … La solution]]
]