Aller au contenu

Programmation C-C++/Passage de paramètres par variable ou par valeur

Un livre de Wikilivres.
En cours d'importationlink={{{link}}}

Ce livre est en cours de copie depuis le site http://casteyde.christian.free.fr/online/cours_cpp/ qui le fournit sous licence GFDL.

Cours de C/C++
^
Pointeurs et références
Notion d'adresse
Notion de pointeur
Référencement, indirection
Notion de référence
Lien entre les pointeurs et les références
Passage de paramètres par variable ou par valeur
Références et pointeurs constants et volatiles
Arithmétique des pointeurs
Utilisation des pointeurs avec les tableaux
Les chaînes de caractères : pointeurs et tableaux à la fois !
Allocation dynamique de mémoire
Pointeurs et références de fonctions
Paramètres de la fonction main - ligne de commande
DANGER

Livre original de C. Casteyde

Passage de paramètres par variable ou par valeur

[modifier | modifier le wikicode]

Il y a deux méthodes pour passer des variables en paramètre dans une fonction : le passage par valeur et le passage par variable. Ces méthodes sont décrites ci-dessous.

Passage par valeur

[modifier | modifier le wikicode]

La valeur de l'expression passée en paramètre est copiée dans une variable locale. C'est cette variable qui est utilisée pour faire les calculs dans la fonction appelée.

Si l'expression passée en paramètre est une variable, son contenu est copié dans la variable locale. Aucune modification de la variable locale dans la fonction appelée ne modifie la variable passée en paramètre, parce que ces modifications ne s'appliquent qu'à une copie de cette dernière.

Le C ne permet de faire que des passages par valeur.

Exemple - Passage de paramètre par valeur

[modifier | modifier le wikicode]
void test(int k) /* k est la copie de la valeur passée en paramètre */
{
     int k;            // on initialise la valeur k//

     printf("saisir k\n"); //affiche à l'écran//

     scanf(%d,&k); //scanne la donnée entrée au clavier et la stocke à l'adresse de k, %d car k est un entier pour tout entier scanné il faut mettre %d , pour un nombre décimal %f par exemple //
}
int main()
{
    int i=2;
    test(i);        /* Le contenu de i est copié dans k. i n'est pas modifié. Il vaut toujours 2. */
    test(2);        /* La valeur 2 est copiée dans k. */
    
    return 0;
}

Passage par variable

[modifier | modifier le wikicode]

La deuxième technique consiste à passer non plus la valeur des variables comme paramètre, mais à passer les variables elles-mêmes. Il n'y a donc plus de copie, plus de variable locale. Toute modification du paramètre dans la fonction appelée entraîne la modification de la variable passée en paramètre.

Le C ne permet pas de faire ce type de passage de paramètres (le C++ le permet en revanche).

Exemple - Passage de paramètre par variable en Pascal

[modifier | modifier le wikicode]
Var i : integer;

Procedure test(Var j : integer)
Begin
            {La variable j est strictement égale
             à la variable passée en paramètre.}
   j:=2;    {Ici, cette variable est modifiée.}
End;

Begin
   i:=3;    {Initialise i à 3}
   test(i); {Appelle la fonction. La variable i est passée en
             paramètres, pas sa valeur. Elle est modifiée par
             la fonction test.}

     {Ici, i vaut 2.}
End.

Puisque la fonction attend une variable en paramètre, on ne peut plus appeler test avec une valeur (test(3) est maintenant interdit, car 3 n'est pas une variable : on ne peut pas le modifier).

Avantages et inconvénients des deux méthodes

[modifier | modifier le wikicode]

Les passages par références sont plus rapides et plus économes en mémoire que les passages par valeur, puisque les étapes de la création de la variable locale et la copie de la valeur ne sont pas faites. Il faut donc éviter les passages par valeur dans les cas d'appels récursifs de fonction ou de fonctions travaillant avec des grandes structures de données (matrices par exemple).

Les passages par valeurs permettent d'éviter de détruire par mégarde les variables passées en paramètre. Si l'on veut se prévenir de la destruction accidentelle des paramètres passés par variable, il faut utiliser le mot clé const. Le compilateur interdira alors toute modification de la variable dans la fonction appelée, ce qui peut parfois obliger cette fonction à réaliser des copies de travail en local.

Comment passer les paramètres par variable en C ?

[modifier | modifier le wikicode]

Il n'y a qu'une solution : passer l'adresse de la variable. Cela constitue donc une application des pointeurs.

Voici comment l'Exemple 4-5 serait programmé en C :

Exemple - Passage de paramètre par variable en C

[modifier | modifier le wikicode]
void test(int *pj) /* test attend l'adresse d'un entier... */
{
    *pj=2;          /* ... pour le modifier. */
    return;
}

int main(void)
{
    int i=3;
    test(&i);       /* On passe l'adresse de i en paramètre. */
    /* Ici, i vaut 2. */
    return 0;
}

À présent, il est facile de comprendre la signification de & dans l'appel de scanf : les variables à entrer sont passées par variable.

Passage de paramètres par référence/adresse

[modifier | modifier le wikicode]

La solution du C est exactement la même que celle du Pascal du point de vue sémantique. En fait, le Pascal procède exactement de la même manière en interne, mais la manipulation des pointeurs est masquée par le langage. Cependant, plusieurs problèmes se posent au niveau syntaxique :

  • la syntaxe est lourde dans la fonction, à cause de l'emploi de l'opérateur * devant les paramètres ;
  • la syntaxe est dangereuse lors de l'appel de la fonction, puisqu'il faut systématiquement penser à utiliser l'opérateur & devant les paramètres. Un oubli devant une variable de type entier et la valeur de l'entier est utilisée à la place de son adresse dans la fonction appelée (plantage assuré, essayez avec scanf).

Le C++ permet de résoudre tous ces problèmes à l'aide des références. Au lieu de passer les adresses des variables, il suffit de passer les variables elles-mêmes en utilisant des paramètres sous la forme de références. La syntaxe des paramètres devient alors :

type &identificateur [, type &identificateur [...]]

Exemple - Passage de paramètre par référence en C++

[modifier | modifier le wikicode]
void test(int &i) // i est une référence du paramètre.
{
    i = 2;    // Modifie le paramètre passé en référence.
    return;
}

int main(void)
{
    int i=3;
    test(i);
    // Après l'appel de test, i vaut 2.
    // L'opérateur & n'est pas nécessaire pour appeler
    // test.
    return 0;
}

Il est recommandé, pour des raisons de performances, de passer par référence tous les paramètres dont la copie peut prendre beaucoup de temps (en pratique, seuls les types de base du langage pourront être passés par valeur). Bien entendu, il faut utiliser des références constantes au maximum afin d'éviter les modifications accidentelles des variables de la fonction appelante dans la fonction appelée. En revanche, les paramètres de retour des fonctions ne devront pas être déclarés comme des références constantes, car on ne pourrait pas les écrire si c'était le cas.

Exemple - Passage de paramètres constants par référence

[modifier | modifier le wikicode]
typedef struct
{
    ...
} structure;

void ma_fonction(const structure & s)
{
    ...
    return ;
}

Dans cet exemple, c'est une référence sur une structure constante. Le code se trouvant à l'intérieur de la fonction ne peut donc pas utiliser la référence s pour modifier la structure (on notera cependant que c'est la fonction elle-même qui s'interdit l'écriture dans la variable s. const est donc un mot clé « coopératif ». Il n'est pas possible à un programmeur d'empêcher ses collègues d'écrire dans ses variables avec le mot clé const. Nous verrons dans le Chapitre 8 que le C++ permet de pallier ce problème grâce à une technique appelée l'encapsulation.).

Un autre avantage des références constantes pour les passages par variables est que si le paramètre n'est pas une variable ou, s'il n'est pas du bon type, une variable locale du type du paramètre est créée et initialisée avec la valeur du paramètre transtypé.

Exemple - Création d'un objet temporaire lors d'un passage par référence

[modifier | modifier le wikicode]
void test(const int &i)
{
    ...       // Utilisation de la variable i
              // dans la fonction test. La variable
              // i est créée si nécessaire.
    return ;
}

int main(void)
{
    test(3);   // Appel de test avec une constante.
    return 0;
}

Au cours de cet appel, une variable locale est créée (la variable i de la fonction test), et 3 lui est affectée.