Paramètres par valeur

Regardons de plus près les sous-programmes et leurs paramètres...

Le prototype

La syntaxe propre à un prototype de sous-programme est la suivante:

Quelques exemples de prototypes de fonctions:

// Fonction Carre ()
// Intrants: Nombre (un entier)
// Extrants: un entier (le carré de Nombre)
// Rôle: Calcule et retourne le carré du nombre Nombre
int Carre (int Nombre);

// Fonction Puissance ()
// Intrants: Nombre (un réel)
//  Exposant (un entier)
// Extrants: un réel (Nombre élevé à la puissance Exposant)
// Rôle: Calcule et retourne Nombre élevé à la puissance
//  Exposant
float Puissance (float Nombre, int Exposant);

// Fonction ValeurAbsolue()
// Intrants: Nombre (un entier)
// Extrants: un entier (la valeur absolue de Nombre)
// Rôle: Calcule et retourne la valeur absolue de Nombre
int ValeurAbsolue (int Nombre);

Quelques exemples de prototypes de procédures:

// Procédure Dessiner_Ligne ()
// Intrants: Nombre_Car (un entier), le nombre de caractères
// à dessiner
// Symbole (un caractère), le caractères à dessiner
// Extrants: aucun
// Rôle: Affiche une ligne faite de Nombre_Car fois le
// caractère Symbole
void Dessiner_Ligne (int Nombre_Car, char Symbole);

// Procédure Afficher_Menu()
// Intrants: aucun
// Extrants: aucun
// Rôle: Affiche le menu principal de l'application
void Afficher_Menu ();

 

Définition

Le prototype d'un sous-programme informe le programme de l'existence de ce sous-programme, et de sa signature (nom du sous-programme, nombre de paramètres, type des paramètres). On peut dire que le prototype décrit le sous-programme.

 

La définition

La syntaxe propre à la définition d'un sous-programme est la suivante:


Voici un exemple de définition de la fonction «Carre()»:

// Fonction Carre ()
// Intrants: Nombre  (un entier)
// Extrants: un entier   (le carré de Nombre)
// Rôle: Calcule et retourne le carré du nombre Nombre
int Carre (int Nombre)
{
   int Resultat;
   Resultat = Nombre * Nombre;
   return Resultat;
} // Carre (int)

Remarquez que le type de la variable «Resultat» («int») correspond au type de la fonction. L'énoncé «return» fera en sorte d'attribuer à la fonction «Carre()» la valeur se trouvant dans la variable «Resultat» au moment de l'énoncé «return».

Note: il existe plus d'une façon d'écrire un même sous-programme. Essayez d'écrire la fonction «Carre()» ci-dessus de manière différente...


Tracer un appel à la fonction «Carre()»

Soit le programme C++ suivant:

#include <iostream>
using namespace std;

// Fonction Carre ()
// Intrants: Nombre (un entier)
// Extrants: un entier (le carré de Nombre)
// Rôle: Calcule et retourne le carré du nombre Nombre
int Carre (int Nombre); // prototype de la fonction

// Programme principal
// Lire un entier, calcule son carré, et l'affiche
void main ()
{
   int Valeur, // pour lire l'entrée faite par l'usager
       Carre_De_Valeur; // pour saisir le résultat du traitement

   cout << "Veuillez entrer un nombre s.v.p.: ";
   cin >> Valeur;
   Carre_De_Valeur = Carre (Valeur); // appel de la fonction
   cout << "Le carré de "
        << Valeur
        << " est: "
        << Carre_De_Valeur
        << endl;
} // programme principal

// Fonction Carre ()
// Intrants: Nombre (un entier)
// Extrants: un entier (le carré de Nombre)
// Rôle: Calcule et retourne le carré du nombre Nombre
int Carre (int Nombre)
{
   int Resultat;
   Resultat = Nombre * Nombre;
   return Resultat;
}

Présumant que l'usager entre la valeur «6» au «cin>> Valeur;», voici concrètement ce qui se produira lors de l'appel de la fonction «Carre()».

Étape 0: avant l'appel

La fonction «Carre()» est appelée à la 5ième ligne du programme principal. Nous en sommes donc, au moment d'entreprendre l'appel de la fonction, dans le bloc d'instructions du «main()».


Valeur
Carre_de_Valeur
Présumant toujours que l'usager ait entré la valeur «6» à la suite de l'invitation «Veuillez entrer un nombre s.v.p.», les valeurs des variables déclarées dans le bloc d'instruction du «main()» auront respectivement comme valeur, au moment de l'appel:
6
?

Étape 1: l'appel lui-même

Appeler une fonction implique une certaine mécanique, sur laquelle nous poserons un regard intéressé mais «de haut niveau»[1].
Pourquoi un appel?

Une fonction est un sous-programme qui a de particulier qu'il accomplit une tâche--que l'on veut précise et bien définie--et prend la valeur de son résultat.

Définition

Lorsqu'un sous-programme (comme le «main()») a recours aux services d'une fonction, on dit que celui-ci appelle la fonction.

Dans un morphogramme, on représentera une fonction comme s'imbriquant dans une séquence en tant qu'opération (comprenant une affectation, puisqu'elle produit un résultat d'un type donné).

La fonction est toutefois une opération dite complexe, qu'il faut éventuellement détailler; ce n'est pas une opération simple ou élémentaire comme le sont la lecture, l'écriture et l'affectation. Une fonction n'est pas une primitive.


L'appel de fonction a donc pour effet de créer un saut dans le programme, du bloc d'instructions de l'appelant (le «main()» ici) au bloc d'instructions de la fonction appelée. Schématiquement:


Portée des variables

Il est important à ce stade-ci de parler de la portée des variables. Une variable n'existe que de la ligne à laquelle elle est déclarée à l'accolade fermante du bloc dans lequel elle est déclarée, inclusivement. On dit qu'elle est locale à ce bloc.

Ainsi, les variables déclarées dans le sous-programme «main()» sont locales à ce sous-programme, et n'existent pas pour la fonction «Carre()»; cette dernière ne pourrait donc pas accéder à la variable «Valeur», par exemple, qui n'existe que pour la procédure «main()».

De la même manière, les variables déclarées dans la fonction «Carre()» n'existent que pour cette fonction. Ainsi, le «main()» ne pourrait pas utiliser la variable «Resultat» de la fonction «Carre()», car pour lui, cette variable n'existe pas.

Note: pour la même raison, si le «main()» déclarait une variable nommée «Variable_Drole» et la fonction «Carre()» déclarait aussi une variable nommée «Variable_Drole», il y aurait deux variables différentes du même nom, mais aucun risque de conflit, car le «main()» ne connaîtrait que sa propre «Variable_Drole», et il en serait de même pour la fonction «Carre()».


Les paramètres

Puisqu'un sous-programme n'a pas accès aux variables locales d'un autre sous-programme, il faut un mécanisme pour que les sous-programmes puissent «se parler», échanger des données.

Dans le cas de l'appel par «main()» de «Carre()», il faut un mécanisme pour que le «main()» puisse indiquer à la fonction «Carre()» la valeur dont il désire connaître le carré.

Bien entendu, ce mécanisme de communication existe; c'est ce qu'on nomme la mécanique du passage de paramètres.

Définition

Le passage de paramètres est le mécanisme par lequel un sous-programme appelant informe le sous-programme appelé des données avec lesquelles ce dernier devra travailler[2].

Regardons un peu comment cela fonctionne:

Il est important de relever que ce n'est que la valeur de la variable «Valeur» qui est copiée dans la variable «Nombre» lors du passage de paramètre; le paramètre «Nombre» du sous-programme «Carre()» deviendra une copie de la variable «Valeur» du sous-programme «main()».

Si la fonction «Carre()» modifiait le contenu de la variable «Nombre», cela ne changerait en rien le contenu de la variable «Valeur» du sous-programme «main()». Ce sont deux variables différentes, bien qu'elles contiennent à un certain moment deux copies distinctes d'une même valeur.

Étape 2: fin de l'appel, et énoncé de retour

Lorsque la fonction termine son exécution, elle doit souligner au sous-programme l'ayant appelé du résultat qu'elle a produit. On dira alors que la fonction prend une valeur--celle de son résultat--ou alors qu'elle retourne une valeur.

En C++, l'énoncé signifiant la fin de l'exécution d'une fonction et la production de son résultat utilise le mot clé «return».

Ce que fait l'énoncé «return»

L'énoncé «return» en C++ fait en sorte qu'une valeur soit attribuée à la fonction. Par exemple, pour la fonction «Carre()», nous avons:

Poursuivant notre exemple, puisque «Nombre» s'était vu confier, à l'appel de la fonction, la valeur «6», la variable «Resultat» contiendra la valeur «36» (résultat de l'opération «Nombre * Nombre»).

À ce moment, l'appel «Carre(Valeur)» (donc «Carre(6)») du «main()» prend donc la valeur «36». L'affectation «Carre_De_Valeur = Carre (Valeur);» fait en sorte que la valeur «36», résultat de cet appel à «Carre()» soit affectée à la variable «Carre_De_Valeur».

Le schéma suivant montre le chemin que prend la valeur de retour d'une fonction:

 


[1] Voir ce cours pour en savoir plus sur mécanique interne d'appels de fonction.
[2] Cette définition est incomplète, mais nous suffira pour le moment.