Nous avons déjà présenté des exemples très simples de procédures (sans paramètres).
Rappelons que les procédures représentent une catégorie de sous-programmes. Elles se distinguent des fonctions, une autre catégorie de sous-programme que nous aborderons ultérieurement.
L'objectif de cette partie du cours est de présenter les procédures de manière générale. Nous allons donc aborder ici le cas des procédures avec paramètres que nous introduiront par un exemple.
Pour montrer l'intérêt des paramètres dans un sous-programme, nous allons utiliser deux versions d'un même projet (sans intérêt autre que pédagogique !) qui permet d'afficher l'adresse mail d'une personne de nom et prénom donné.
Voici les différents choix proposés par la boucle de saisie des données:
Et voici, un exemple d'exécution lorsque l'utilisateur choisit Mail SFR:
La première version du projet utilise uniquement des procédures sans paramètres. Elle se trouve dans le dossier
Exemple-C-Sous-Programme/Mail-Version1
La deuxième version utilise une procédure paramètrée. Elle se trouve dans le dossier
Exemple-C-Sous-Programme/Mail-Version2
La première version contient deux procédures nommées AdresseMail_SFR et AdresseMail_Free. Elles sont appelées dans le corps du programme principal:
int main(int argc, char** argv) { while (choix!=3) { .... cout << "\nVotre choix : "; cin >> choix; switch (choix){ case 1: AdresseMail_SFR (); break; case 2: AdresseMail_Free (); break; } ....
Voici le code de la première procédure :
void AdresseMail_SFR () { string prenom, nom; cout << "\nPrenom : "; cin >> prenom; cout << "Nom : "; cin >> nom; cout << "Adresse mail : " << prenom+"."+nom+"@sfr.fr"; }
et voici celle de la seconde:
void AdresseMail_Free () { string prenom, nom; cout << "\nPrenom : "; cin >> prenom; cout << "Nom : "; cin >> nom; cout << "Adresse mail : " << prenom+"."+nom+"@free.fr"; }
On constate que ces deux procédures se ressemblent énormément. Mise à part le nom de la procédure, la seule chose qui change est le nom de l'opérateur (en blanc dans le code ci-dessus) dans l'affichage de l'adresse mail.
En gros, on écrit deux fois le même code pour faire la même chose.
En utilisant une procédure paramètrée, on peut éviter cette répétition inutile de code. C'est ce que nous avons fait dans la deuxième version du projet.
Voici cette procédure:
void AfficherLeMailChez (string operateur) { string prenom, nom; cout << "\nPrenom : "; cin >> prenom; cout << "Nom : "; cin >> nom; cout << "Adresse mail : " << prenom+"."+nom+"@"+operateur+".fr"; }
Elle permet d'afficher le mail chez n'importe quel opérateur (représenté par le paramètre operateur). Nous avons en quelque sorte généralisé les deux procédures de la première version: pour obtenir l'adresse email, il suffit de concaténer le prénom, la chaine ".", le nom, la chaine "@", l'opérateur et la chaine ".fr".
Le code des deux procédures évènementielles se résume alors à l'appel de cette procédure avec deux valeurs de paramètres différents dans le programme principal:
int main(int argc, char** argv) { while (choix!=3) { ... cout << "Votre choix : "; cin >> choix; switch (choix){ case 1: AfficherLeMailChez("sfr"); break; case 2: AfficherLeMailChez("free"); break; }
Les valeurs des paramètres à l'appel ("sfr" et "free" dans notre exemple) sont les paramètres effectifs à ne pas confondre avec les paramètres formels: ce sont les paramètres déclarés dans l'entête de la procédure. Dans notre exemple, il n'y a qu'un seul paramètre formel (operateur de type string).
Voyons à présent comment tout cela fonctionne à l'exécution. Imaginons que l'utilisateur effectue le choix 2 ( Mail Free ).
En fait, les paramètres formels d'un sous-programme se comportent comme des variables locales. Vous pouvez donc considérer que tout ce qui a été dit au sujet des variables locales s'applique également aux paramètres formels.
Remarque importante : il existe (en C++, comme dans d'autres langage) d'autre modes de passage de paramètres. Notamment C++ permet le passage de paramètre par référence. J'ai choisi de ne pas parler de ce mode de passage de paramètres dans ce cours pour des raisons pédagogiques.
De manière générale, une procédure C++ peut se déclarer de la manière suivante:
void Nom De La Procédure ( liste des paramètres ) { Déclaration des variables locale Instructions }
Rappelons que la première ligne est l'entête de la procédure. Elle est formée du mot clef void suivi du nom de la procédure et d'une liste optionnelle de paramètres entre parenthèses.
La partie entre { et } est le corps de la procédure. Elle contient la déclaration des variables locales (optionnelle) ainsi que les instructions à exécuter.
La liste des paramètres peut être vide ou non. Mais dans tout les cas de figure, les parenthèses sont obligatoires. Une procédure sans paramètre s'écrira donc comme ceci:
void Nom De La Procédure ( ) { Déclaration des variables locales Instructions }
S'il n'y a qu'un seul paramètre, on précise son type puis son nom dans les parenthèses:
void Nom De La Procédure (Type Nom) { Déclaration des variables locales Instructions }
S'il y a plusieurs paramètres, les couples Type Nom de chacun d'eux sont séparés par des virgules:
void Nom De La Procédure (Type Nom, Type Nom, ...., Type Nom) { Déclaration des variables locales Instructions }
Nom De La Procédure ( liste des paramètres effectifs );
Notez que les parenthèses sont obligatoires, même si la procédure ne possède par de paramètres. Un appel de procédure sans paramètre s'écrit donc:
Nom De La Procédure ( );
Dans l'exemple très simple que nous avons présenté, les paramètres effectifs étaient des chaines de caractères et il n'y en avait qu'un seul par appel. De manière générale, il peut y avoir plusieurs paramètres effectifs et d'autre part, ces paramètres effectifs ne sont pas necéssairement de simples littéraux mais peuvent être des expressions. Pour définir la liste des paramètres effectifs de la manière la plus générale possible, nous dirons donc que c'est une liste d'expressions séparées par des virgules:
expression1,...., expressionN
Pour que l'appel de procédure soit compilable (et exécutable !), deux conditions doivent être satisfaites:
Voici par exemple une procédure qui possède trois paramètres formels (p, n ,o) de type chaine de caractères:
void FormerMail (string p, string n , string o) { mail = p+"."+n+"@"+o+".fr"; }
Cette procédure forme une adresse mail à partir de trois paramètres:p (le prénom), n (le nom) et o (l'opérateur). Le résultat est stocké dans la variable globale mail.
En supposant que Prenom1, Prenom2 et Nom soient des variables de type chaine de caractères, elle pourrait être appelée de la manière suivante:
FormerMail (Prenom1+"-"+Prenom2, Nom, "free");
Le premier paramètre effectif est une expression de type chaine de caractères, le deuxième une variable de type chaine de caractères et le troisième, un littéral de type chaine de caractères. Ces trois paramètres effectifs sont donc compatibles avec les paramètres formels.
Par contre, elle ne pourra être appelée d'aucune des manières suivantes:
FormerMail (Prenom1, Nom); FormerMail (Prenom1, Nom, 9);
En effet, dans le premier appel le nombre de paramètres effectifs n'est pas égal au nombre de paramètres formels et dans le deuxième, le type du troisième paramètre effectif (numérique) n'est pas compatible avec le type du troisième paramètre formel.
Pour les paramètres de types numériques, le type d'un paramètre formel peut être différent de celui du paramètre effectif de même position à condition qu'il soit également de type numérique.
Si le paramètre formel est de type float et que le paramètre effectif correspondant est de type int, la valeur du paramètre effectif est simplement recopiée dans le paramètre formel.
Par contre si le paramètre formel est de type int et le paramètre effectif de type float, le paramètre formel contiendra la partie entière du paramètre effectif. Dans ce cas, il y a donc en général une perte de précision.
Nous retrouvons ici les règles définissant une affectation valide.
Un appel de procédure est toujours contenu dans un autre sous-programme, que nous appelerons la sous-programme appelant.
Notez que cela est vrai également pour un appel de procédure se situant dans le corps du programme principal. En effet, comme nous le verrons plus loin, le programme principal n'est rien d'autre qu'un sous-programme nommé main. Il s'agit par contre d'un sous-programme de type fonction.
Les paramètres formels d'un sous-programme peuvent être considérés comme des variables locales. Donc tout ce que nous avons dit sur les variables locales est également valables pour les paramètres. En particulier:
L'exécution d'un appel de procédure provoque les opérations suivantes:
FormerMail (Prenom1+"-"+Prenom2, "Bach", "free");Le premier paramètre effectif est une expression dont la valeur sera évaluée avant d'être transmise au paramètre effectif correspondant (p). En supposant que les variables Prenom1, Prenom2 aient respectivement pour valeur "Jean", "Sebastien" , la valeur retransmise à p sera donc "Jean-Sebastien".