Big Tuto : Apprenez le C++

Chapitre 6 : Les fonctions - 1ère partie

Tutoriel présenté par : Robert Gillard (Gondulzak)
Date d'écriture : 22 octobre 2015
Date de révision : 31 octobre 2015

      Préliminaires 

   A la fin du chapitre 3 de notre Big Tuto C++, nous avons fait une brève introduction concernant les fonctions. Nous avons vu un exemple de fonction qui ne retournait aucune valeur (Projet 019Fonctions1) et un exemple de fonction retournant un entier (020Fonctions2).

   Nous allons ici développer le concept de fonction, tout en gardant à l'esprit que celles abordées dans le chapitre 3 ainsi que celles dont nous parlerons dans ce chapitre et dans le suivant sont des fonctions dites globales. Ces fonctions globales existent en dehors des objets et des classes que nous verrons lors de l'étude de la POO, où les fonctions seront alors appelées méthodes. wink


         1 – Généralités sur les fonctions

      1.1 - Valeurs renvoyées et paramètres d'une fonction

   Nous avons vu dans le chapitre 3 qu'une fonction peut recevoir plusieurs valeurs mais ne peut renvoyer qu'une valeur unique (quoique... frown nous verrons dans le prochain chapitre que l'utilisation de références dans une fonction permet de renvoyer plusieurs valeurs wink) . Le type de valeur de retour d'une fonction peut-être un type quelconque de C++ ou avoir le type void. Nous savons qu'une fonction qui retourne void ne retourne aucune valeur. angel

   Donc, dans la déclaration :

void UneFontion();

   UneFonction() ne reçoit aucun paramètre et ne retourne aucune valeur.

 

  Mais si l'on écrit :

int UneFonction();

   ...nous déclarons que UneFonction() retournera un entier.

 

   Dans la déclaration :

int UneFonction( string chaine, int nombre);

   ...nous voyons qu'une chaîne de caractères ainsi qu'un entier sont passés en paramètres à la fonction et que celle-ci retournera une valeur de type int. Les paramètres passés à la fonction agissent comme des variables qui seront manipulées à l'intérieur de la fonction et sont appelés ''paramètres formels'' ou ''arguments'' de la fonction. wink

 

   La valeur réellement passée à la fonction lors d'un appel de celle-ci s'appelle paramètre réel de la fonction. Ainsi, si dans l'exemple précédent nous décidons de passer 10 ennemis à la fonction, celle-ci pourrait être appelée comme suit:

LoadEnemy(''Gobelin'', 10);

   Dans cet appel de fonction, ''Gobelin'' et ''10'' sont les paramètres réels passés à la fonction. 


      1.2 - Prototype d'une fonction

   Le prototype d'une fonction est la somme des éléments qui entrent dans la déclaration de la fonction.

   Le prototype d'une fonction peut-être représenté par le schéma suivant :

Type de retour Nom_de_la_fonction (Type_paramètre Paramètre_formel...);

 

   Le prototype de la fonction peut inclure plusieurs paramètres formels de types différents mais s' il n'y a pas de paramètres à passer à la fonction, l'intérieur des parenthèses restera vide de toute écriture (avec C++; il n'est plus nécessaire d'y ajouter le mot-réservé ''void'' comme cela était courant avec le langage C wink). D'autre part, la déclaration complète de la fonction devra toujours être suivie d'un point-virgule après la parenthèse fermante.


      1.3 – Déclaration et définition d'une fonction


   Déclaration

   Dans le chapitre 3 nous avons vu qu'une fonction devait obligatoirement être déclarée avant la fonction main() si elle était définie après celle-ci. En effet, une fonction appelée depuis main() et non définie auparavant ne sera pas connue de la fonction principale d'un programme et donnera lieu à une erreur de compilation. Pour éviter ceci il est possible de définir toutes les fonctions avant la fonction main(), de cette façon la définition tiendra également lieu de déclaration.

   Si vous avez plusieurs fonctions à déclarer, il est cependant fortement recommandé de faire les déclarations dans un fichier séparé qui sera appelé depuis le programme principal par un #includecheeky


   Définition

   La définition d'une fonction est le corps de la fonction, c'est-à-dire tout ce qui se trouve entre la première accolade ouvrante et la dernière accolade fermante. Je parle de première et de dernière accolade car une fonction peut-être constituée de plusieurs blocs, eux-mêmes inclus entre une accolade ouvrante et une accolade fermante. wink

 

Par exemple :
Type_de_retour Nom_de_la_fonction (Type_paramètre param1, Type_paramètre param2)
{
// Déclarations de variables éventuelles

// Instructions
 
// Bloc éventuel 1
{
Instructions bloc 1
}
 
// Bloc éventuel 2
{
Instructions bloc 2
}
// Instruction de retour
}


      1.4 – Domaine de visibilité des variables

   Variables locales

   Dans une fonction, le domaine de visibilité, appelé également ''étendue de validité'' d'une variable, se limite à la fonction elle-même ; on parle aussi de ''portée d'une variable''.

   En effet, reprenons l'exemple du chapitre 3, projet 020Fonctions2, ''Une fonction qui retourne un entier'' :

 

//Projet 020Fonctions2
#include <iostream>
 
using namespace std;
 
 
// Une fonction qui retourne un entier
int Addition(int x, int y)
{
int resultat(0);
resultat = x + y;
 
return resultat;
}

 

   Lorsque dans la fonction main(), l'appel à la fonction Addition() sous la forme :

somme = Addition(a, b);

   est exécutée, les valeurs contenues dans les variables a et b sont copiées dans les variables de fonction x et y. Hormis ce fait, il n'existe aucune autre connexion entre les valeurs x et y de la fonction et les variables a et b de la fonction main(). Ces variables possèdent des places différentes dans la mémoire de l'ordinateur et les paramètres passés à la fonction, considérés comme variables locales, sont utilisés comme s'ils avaient été définis dans le corps de la fonction.

   Les variables locales créées dans le corps d'une fonction sont également définies par le terme de variables automatiques. Prenons le cas de la variable resultat, créée dans la fonction Addition(). Il s'agit d'une variable locale (ou automatique). Les variables automatiques sont créées sur la pile de l'ordinateur et c'est ici que commence leur visibilité car elles sont empilées lors de leur création (instruction ''push'' en assembleur) et y restent jusqu'à ce que l'accolade fermante de la fonction dans laquelle elles existent soit rencontrée. Le pointeur de pile est alors déplacé au dessus et les variables déclarées dans la fonction sont enlevées de la pile et deviennent inaccessibles (instruction ''pop'' en assembleur). On dit qu'elles sont sorties de leur étendue de validité.

 

Retrouvez les projets complets de ce chapitre :

 

   Voyons maintenant un exemple de domaine de visibilité de diverses variables dans la cas d'une fonction qui, elle même, contient un bloc. (Projet 040Fonctions3

//Projet 040Fonctions3
//Domaine de définition de variables dans une fonction
#include <iostream>
 
using namespace std;
using uint = unsigned int;
 
//On déclare une fonction ne prenant aucun paramètre et qui retourne void
void UneFonction();
 
 
int main()
{
uint var1 = 25;
cout << "Dans main(), avant l'appel a UneFonction, var1 = " << var1 << endl << endl;
//Appel de UneFonction()
UneFonction();
 
cout << endl;
cout << "Dans main(), apres l'appel a UneFonction, var1 = " << var1 << endl;
 
cin.get();
return 0;
}
 
// Implémentation (définition de la fonction)
void UneFonction()
{
uint var1(10); // variable locale
uint var2(20); // variable locale
cout << "Dans UneFonction(), avant Bloc1, var1 = " << var1 << endl;
cout << "Dans UneFonction(), avant Bloc1, var2 = " << var2 << endl << endl;
 
{
// Bloc 1
cout << "Dans UneFonction(), a l'interieur de Bloc1, var1 = " << var1 << endl;
cout << "Dans UneFonction(), A l'interieur de Bloc1, var2 = " << var2 << endl << endl;
 
// Initialisation d'une nouvelle variable var1
cout << "Dans UneFonction(), on declare une nouvelle variable var1 a l'interieur de Bloc1";
uint var1(5); // variable locale à Bloc1
cout << "Dans UneFonction(), a l'interieur de Bloc1, var1 = " << var1 << endl << endl;
}
 
cout << "Dans UneFonction(), apres Bloc1, var1 = " << var1 << endl;
cout << "Dans UneFonction(), apres Bloc1, var2 = " << var2 << endl;
}

 

   Ce qui donne dans la console :

 

   Donnons ici quelques explications. cool

   Afin de mieux se rendre compte du domaine de définition des variables locales, nous n'utilisons que deux variables de type ''unsigned int'' dénommées ''var1" et ''var2''.

   Dans la fonction main(), nous déclarons un entier non signé ''var1'' auquel nous donnons 25 pour valeur.
   Au début de la fonction UneFonction(), nous déclarons ''var1'' et ''var2'' comme variables locales ayant respectivement 10 et 20 pour valeurs.
   A l'intérieur de bloc 1, nous initialisons une nouvelle variable ''var1'' à qui on donne la valeur 5.

 

   Qu'apercevons-nous ? surprise

   Au début de UneFonction(), var1 et var2 sont placées sur la pile dès leur initialisation, et restent visibles pendant toute l'exécution de la fonction (var1 et var2 ont les mêmes valeurs avant le bloc et après le bloc compris entre les accolades ouvrante et fermante). wink

   Remarquez que var2 est également visible dans le bloc, mais var1 existe aussi !! angel Nous avons simplement initialisé une autre variable portant le même nom à l'intérieur du bloc. wink Cette nouvelle variable var1 ''masque'' la variable var1 définie avant le bloc. Cette nouvelle variable est mise sur la pile dès sa création et immédiatement enlevée à la sortie du bloc où elle devient inaccessible. C'est pourquoi nous retrouvons la valeur (10) de notre variable var1 initialisée avant le bloc. cheeky

   Quant à la variable var1 définie dans la fonction main(), celle-ci est visible dans toute la fonction mais est également masquée à l'intérieur de UneFonction().

   Il est important de retenir que les trois variables ''var1'', définies à des endroits différents du programme, sont des variables complètement différentes, bien que portant le même nom. En effet, elles ont un emplacement différent en mémoire et vous pourrez le vérifier par vous-même. wink


   Variables globales

   Dans un programme, nous pouvons utiliser des variables dites ''globales''. Les variables globales sont déclarées et initialisées en début de programme, avant la fonction main() et leur domaine de visibilité s'étend au programme tout entier.

   Si la variable a été définie dans un fichier séparé, nous devons ajouter le mot-clé ''extern'' devant le type et le nom de la variable sous forme de déclaration en début de programme (cette procédure est tout-à-fait identique à celle déjà utilisée par le langage C wink).

   Pour pouvoir accéder à une variable globale à partir d'une fonction du programme, variable déjà déclarée dans le programme lui-même, nous devrons placer, à la gauche du nom de la variable, le signe de l'opérateur de résolution de portée ''::''. Nous allons voir un exemple d'utilisation de variable globale dans une fonction qui divise deux nombres :

 

   Projet 041Fonctions4 :   

//Projet 041Fonctions4
//Utilisation d'une variable globale à partir d'une fonction
#include <iostream>
 
using namespace std;
 
float divise(float x, float y); // déclaration de la fonction
 
float num(30); // variable globale
 
 
int main()
{
// variables locales
float quotient(0);
float num(24);
float denom(6);
 
cout << "Resultat de la division avec le numerateur en variable locale" << endl;
quotient = divise(num, denom);
cout << "La division de " << num << " / " << denom << " = " << quotient << endl << endl;
 
cout << "Resultat de la division avec le numerateur en variable globale" << endl;
quotient = divise(::num, denom);
cout << "La division de " << ::num << " / " << denom << " = " << quotient;
 
cin.get();
return 0;
}
 
float divise(float numerateur, float denominateur)
{
return numerateur / denominateur;
}

 

   Ce qui donne dans la console :

 

   Nous utilisons la fonction divise(x, y) deux fois. Une première fois en passant à la fonction la variable locale ''num'' définie dans la fonction elle-même et une seconde fois, en passant à la fonction la variable globale ''num'' déclarée et définie en debut de programme. Pour atteindre cette variable globale, nous la faisons précéder de l'opérateur de résolution de portée '::'.


   Variables statiques

   Nous savons qu'une variable locale cesse d'exister lorsqu'elle sort de son étendue de validité à la fin de l'exécution d'une fonction. Il existe cependant plusieurs cas où nous aurions besoin qu'une variable puisse conserver son existence au sortir d'une fonction. Un cas qui nous vient immédiatement à l'esprit est celui qui compte le nombre de visites de votre site préféré ou encore, le nombre de fois qu'une fonction est appelée pendant l'exécution d'un programme. wink

   De telles variables existent, en C++ comme en C, ce sont les variables dites ''statiques locales'' ou plus simplement ''variables statiques''. Une telle variable est déclarée comme suit à l'intérieur d'une fonction :

static type_variable nom_variable;

   Il n'y a pas besoin d'initialiser une variable statique, elles sont automatiquement initialisées à 0. wink

 

   Nous allons voir un exemple simple de programme qui compte le nombre de fois qu'une certaine fonction est appelée dans un programme mais auparavant je voudrais parler d'un type également utilisé par C++ et dont nous n'avons pas encore fait mention jusqu'à présent. cool

   Nous savons que pour accéder aux élément d'un vector ou d'un tableau nous pouvons utiliser des variables de type ''int'' dans un intervalle donné. C++ nous permet cependant de définir des variables ayant le type ''size_t''. Le type size_t est un type défini comme ''unsigned'' et est garanti comme étant assez grand pour contenir des valeurs de n'importe quel objet en mémoire. Le type size_t est défini dans le fichier <cstddef>, qui remplace l'ancien fichier de la librairie C stddef.h mais que vous n'êtes pas obligés d'inclure à votre projet (avec Visual Studio 2013 en tout cas cheeky).

   Je précise cependant qu'il n'est absolument pas obligatoire d'utiliser le type size_t pour initialiser une varaiable statique mais que nous le ferons ici pour illustrer notre exemple.

 

   Projet 042Fonctions5 : 

//Projet 042Fonctions5
//Utilisation d'une variable statique
#include <iostream>
#include <cstddef> //Optionnel
 
using namespace std;
 
size_t nb_occurences(); // déclaration de la fonction
 
int main()
{
for (size_t i = 0; i != 1000; i++)
cout << "La fonction a ete appelee " << nb_occurences() << " fois" << endl;
 
cin.get();
return 0;
}
 
 
size_t nb_occurences()
{
// variable statique locale
static size_t compteur;
 
return ++compteur;
}

 

   Ce qui donne dans la console :

 

   A chaque tour de boucle, la fonction nb_occurences() est exécutée mais la variable statique ''compteur'' ne disparaît pas. Elle est incrémentée d'une unité à chaque appel de la fonction et chaque appel est affiché par tour de boucle. Nous n'aurions pu afficher que la dernière ligne de la console en modifiant la fonction main() comme suit :

 

for (size_t i = 0; i != 1000; i++)
nb_occurences();
cout << "La fonction a ete appelee " << nb_occurences() - 1 << " fois" << endl;

 

   Pour terminer ces considérations générales sur les fonctions; j'ajouterai que le type seul d'une variable peut-être passé en argument dans la déclaration d'une fonction. Nous pourrions par exemple déclarer une fonction comme ceci :

 float divise (float, float);

   où le nom des variables ne serait pas explicitement nommé. Ceci est à éviter dans tous les cas ; pour plus de clarté il est préférable de nommer les variables passées en arguments de manière significative de leur activité au sein de la fonction. 

 

   2. Utilisations spéciales des fonctions :

      2.1 - Fonctions inline :

   Dans une certaine mesure, chaque appel de fonction ralentit quelque peu l'exécution d'un programme. Ce retard d'exécution est souvent très faible et pourrait être ignoré mais certaines applications sensibles au temps d'exécution nécessitent de déclarer l'une ou l'autre fonction ''inline''.

   Une fonction inline (en ligne) est une fonction dont une copie du code machine sera placée à chaque endroit où elle sera appelée dans le programme. Si une fonction inline est appelée 10 fois au cours d'un même programme, le programme compilé contiendra 10 copies du code de cette fonction. wink

   On voit tout de suite que l'allongement du code machine pourrait devenir rapidement important par rapport au gain de temps escompté d'où l'avantage d'implémenter des fonctions inline aussi courtes que possible (1 ou 2 instruction(s)). cheeky

 

Remarque : Même si vous créez une ou plusieurs fonctions inline dans votre programme, c'est le compilateur qui décidera de l'utilité ou non de placer une fonction en ligne.

   Un exemple de déclaration de fonction inline :

inline type_retour nom_fonction (paramètres);

   Nous allons voir un exemple simple de fonction inline qui est appelée 3 fois pour afficher le carré d'un nombre.

 

   Projet 043Fonctions6 : 
//Projet 043Fonctions6
//Utilisation d'une fonction inline
#include <iostream>
 
using namespace std;
typedef unsigned long int ulong;
 
inline ulong carre(ulong x); // déclaration de la fonction inline
 
 
int main()
{
ulong nombre(12);
 
cout << "On donne 12 comme valeur a la variable nombre" << endl;
cout << "et on appelle 3 fois la fonction inline carre(nombre)." << endl << endl;
 
int i(0);
 
do
{
nombre = carre(nombre); // La nouvelle variable «nombre» sera mise au carré
cout << nombre << endl;
++i;
} while (i < 3);
 
cin.get();
return 0;
}
 
 
//Definition de la fonction inline
ulong carre(ulong nombre)
{
return nombre * nombre;
}

 

   Et le résultat dans la console :

 

   Dans un programme contenant des fonctions inline, le compilateur a besoin des définitions, et pas simplement des déclarations. C++ recommande de définir les fonctions inline dans un fichier header (fichier.h) sans risque de multi-définition. Le mot-clé ''inline'' n'est dans ce cas, plus nécessaire. wink


      2.2 – Récursivité

   Une fonction récursive est une fonction qui s'appelle par elle-même. Pourquoi et comment une fonction peut-elle s'appeler elle-même, me demanderez-vous ? surprise Et bien il y a des cas, et notamment dans certains calculs mathématiques et de tris rapides où une fonction dite ''itérative'' c'est à dire une fonction qui utilise une boucle ''for'' ou autre, peut-être plus longue ou plus délicate à implémenter, on utilise alors une fonction ''récursive'' qui simplifiera l'opération. wink

   Rien de tel qu'un exemple pour expliquer le fonctionnement d'une fonction récursive et, parmi les exemples souvent proposés par la littérature informatique, on rencontre le calcul d'une ''factorielle'' ou l'écriture d'une suite de Fibonacci. C'est ce dernier exemple que nous allons utiliser. smiley

   Pour rappel, une suite de Fibonacci est une suite d'entiers dans laquelle chacun d'entre-eux, à partir du second entier, est la somme des deux précédents.

   Si nous prenons 10 entiers la suite sera :

1, 1, 2 , 3, 5, 8, 13, 21, 34, 55, 89

   Le second entier est 1. Il est égal à la somme des deux précédents, soit 1 + 0. Le troisième entier étant égal à 1 + 1 et ainsi de suite. Mais voyons déjà une des écritures possibles du code. wink

 

   Projet 044Fonctions7 : 

//Projet 044Fonctions7
//Fonction récursive appliquée à la suite de Fibonacci
#include <iostream>
 
using namespace std;
 
void Fibonacci(size_t valeur1, size_t valeur2); // déclaration de la fonction
 
 
int main()
{
size_t nombre1(0), nombre2(1);
cout << "Les 20 premiers nombres de la suite de Fibonacci" << endl;
cout << "Chaque nombre est la somme des 2 precedents" << endl << endl;
 
// On affiche nombre2 (1)
cout << nombre2 << endl;
 
// 1er appel à la fonction
Fibonacci(nombre1, nombre2);
 
cin.get();
return 0;
}
 
 
void Fibonacci(size_t val1, size_t val2)
{
// variable locale static, automatiquement initialisée à 0
static size_t compteur;
size_t val3(0);
 
if (compteur++ == 20)
return;
 
val3 = val1 + val2;
// On affiche un nombre de la suite
cout << val3 << endl;
 
// La fonction s'appelle elle-même (c'est la récursivité)
Fibonacci(val2, val3);
}

 

   Et le résultat dans la console :

 

 

   Dans ce programme, on définit les variables ''nombre1'' et ''nombre2'' avec les valeurs respectives de 0 et 1 et ces valeurs seront passées une première fois à la fonction Fibonacci(). Lors de cet appel, la variable val3, déclarée dans la fonction Fibonacci() prend la valeur val1 + val2, c'est-à-dire 0 +1 = 1. La fonction s'appelle ensuite par récursivité avec les valeurs val2 et val3 en paramètres.

   Chaque appel à la fonction Fibonacci() génère un nouvel ensemble de variables locales val1, val2 et val3. Aucune d'entre-elles n'est la même que l'une des variables de même nom créées la dernière fois que la fonction a été appelée. wink

   Le programme s'arrête dès que la variable static ''compteur'' atteint la valeur 20. Nous aurions pu utiliser une variable globale pour conserver la trace du nombre de fois où la fonction Fibonacci() s'appelle elle-même, mais comme nous venons précédemment d'introduire la notion de variable statique, c'est cette dernière qui a été préférée comme variable locale dans la fonction. wink

Attention : dans ce cas, la variable ''compteur'' devait impérativement être déclarée ''static''. En effet, déclarée variable locale non-statique, elle aurait été détruite à chaque appel de la fonction, réinitialisée avec une valeur inconnue, ce qui aurait conduit à un débordement de pile (stack overflow) et qui aurait planté notre programme... angry Je sais que le mot ''récursivité'' ne fait pas rire tout le monde, mais bien réfléchie, une fonction récursive peut apporter un style de programmation ''élégant'' tout en gardant une certaine puissance d'exécution. wink

      2.3 – Surcharge de fonctions

   Le langage C++, contrairement au C, permet la surcharge de fonction. Il peut être intéressant que plusieurs fonctions ayant des types de retour et des paramètres différent aient des noms identiques. wink
   On parle alors de ''surcharge de fonctions'', celles-ci sont dites ''fonctions surchargées''.

   Nous n'avons pas encore vu les pointeurs et les références dans les fonctions et c'est pourquoi nous allons voir un exemple simple de fonctions surchargées qui ne font qu'afficher un objet de type différent. smiley

   Projet 045Fonctions8 : 

//Projet 045Fonctions8
//Surcharge de fonction
#include <iostream>
#include <string>
 
using namespace std;
 
// déclaration des fonctions
void affiche(const char c);
void affiche(const string str);
void affiche(const int nb);
void affiche(const long nb1, const long nb2);
 
 
int main()
{
const char c = 'A';
const string chaine {"L'Archimage du Castel de Pointenoire"};
const int valeur = 12;
const long val1(10);
const long val2(15);
 
cout << "Surcharge de fonctions" << endl << endl;
 
affiche(c);
affiche(chaine);
affiche(valeur);
affiche(val1, val2);
 
cin.get();
return 0;
}
 
//Déclarations
void affiche(const char caractere)
{
cout << "Fonction affiche(char) affiche : " << caractere << endl;
}
 
void affiche(const string str)
{
cout << "Fonction affiche(string) affiche : " << str << endl;
}
 
void affiche(const int nombre)
{
cout << "Fonction affiche(int) affiche : " << nombre << endl;
}
 
void affiche(const long valeur1, const long valeur2)
{
cout << "Fonction affiche(long, long) affiche : " << valeur1 + valeur2 << endl;
}

 

   Résultat dans la console :

 

 

   Nous déclarons quatre fonctions ayant ''void'' comme type de retour et qui portent le même nom : ''affiche''. Dans chaque fonction, des arguments constants passés en paramètres ont des types différents et deux arguments de type ''long'' sont passés à la dernière fonction. Chacune d'entre elles ne fait qu'afficher son (ses) argument(s) passé(s) en paramètre(s) et bien que ceux-ci soient de type ou de quantités différents, nous n'attribuons qu'un seul nom à la fonction. C'est le principe de ''surcharge'' que permet le C++. wink

Remarque : dans cet exemple, les variables passées en paramètres sont des constantes dites de ''haut niveau'' (top-level const). Une constante top-level s'applique aux objets eux-mêmes et il est impossible de changer la valeur des paramètres dans les fonctions correspondantes sans provoquer une erreur de compilation.
Nous reviendrons sur ce concept de surcharge de fonction dès le prochain chapitre quand nous aurons introduit l'utilisation des pointeurs et des références dans les fonctions.

      2.4 – Paramètres par défaut

   Jusqu'à présent, nous avons vu que la déclaration d'une fonction pouvait contenir plusieurs paramètres (de même type ou de types différents) et qu'à chacun de ces types devait correspondre un type identique lors de la définition de cette fonction dans le programme. Nous savons aussi qu'il n'est pas nécessaire de donner le même nom à un paramètre dans la déclaration et dans l'en-tête d'une fonction car c'est la position de l'argument dans la déclaration de la fonction qui affecte sa valeur et pas son nom.


   C++ permet cependant de définir des fonctions ayant moins d'arguments que le nombre de paramètres de la déclaration d'une même fonction. Si par exemple vous définissez une fonction avec trois paramètres, vous pouvez appeler cette fonction en lui passant soit trois, deux ou même un seul argument.

   La seule obligation à respecter dans ce cas est que si un paramètre n'a pas de valeur par défaut, aucun autre le précédant ne peut en avoir, ce qui veut dire que tous les paramètres initialisés doivent se trouver à la suite du (des) paramètre(s) non initialisé(s).

   Un simple exemple d'addition de 3 nombres va rapidement éclairer ce sujet. wink

 

   Projet 046Fonctions9 : 

//Projet 046Fonctions9
//Paramètres par défaut dans une fonction
#include <iostream>
 
using namespace std;
 
// Déclaration de la fonction Somme
// Le paramètre valeur1 n'a pas de valeur par défaut ET précède les deux autres
double Somme (int valeur1, int valeur2 = 10, int valeur3 = 15);
 
 
int main()
{
int nombre1(5), nombre2(7), nombre3(9);
double resultat(0);
 
resultat = Somme(nombre1, nombre2, nombre3);
cout << " Passage de nombre1, nombre2, nombre3 en parametres : resultat = " << resultat << endl;
 
resultat = Somme(nombre1, nombre2);
cout << " Passage de nombre1, nombre2 en parametres : resultat = " << resultat << endl;
 
resultat = Somme(nombre1);
cout << " Passage de nombre1 en parametre : resultat = " << resultat << endl;
 
cin.get();
return 0;
}
 
 
double Somme(int nb1, int nb2, int nb3)
{
return static_cast<double>(nb1 + nb2 + nb3);
}

 

   Et les résultats dans la console : 

 

   Dans cet exemple, nous déclarons une fonction ''Somme'' de type ''double'' avec 3 paramètres de type ''int'' dont le second et le troisième sont initialisés par défaut.. Dans l'implémentation de la fonction ''Somme'', nous retournons la somme de trois valeurs ; ces valeurs sont initialisées au début de la fonction main().

   La fonction ''Somme'' est appelée trois fois, une première fois en lui passant les trois paramètres, une seconde fois en passant deux paramètres et une troisième fois en n'en passant qu'un.

 

   Voyons les résultats :

   Au premier appel, les valeurs 5, 7 et 9 des variables initialisées au début de la fonction main() sont additionnées, le résultat donne 21.
   Au second appel de la fonction, il manque le troisième paramètre : les deux premiers paramètres passés sont ceux initialisés au début de la fonction main(), soit 5 et 7. Le troisième paramètre est celui initialisé dans la déclaration de la fonction ''Somme'', soit 15. Et 5 + 7 + 15 est bien égal à 27.

   Au troisième appel de la fonction, il manque les deuxième et troisième paramètres : le premier paramètre est celui initialisé au début de la fonction main(), soit 5. Les deuxième et troisième paramètres sont ceux initialisés au début de la fonction ''Somme'', soit 10 et 15 . Et 5 + 10 + 15 est bien égal à 30.

 

Remarque : Dans cet exemple, le type de retour de la fonction ''Somme'' étant un type ''double'' et les paramètres passés à la fonction étant de type ''int'', nous devons forcer le type ''int'' de la valeur de retour en ''double'', ce que nous faisons avec l'instruction :

return static_cast<double>(nb1 + nb2 + nb3);

   qui pouvait également s'écrire :

return static_cast<double>(nb1) + nb2 + nb3;

   Voir le chapitre 2, paragraphe 3 – Variables (conversion forcée de type) pour plus d'infos. wink

   Ce premier chapitre sur les fonctions est terminé mais il y a encore beaucoup à en dire et notamment sur les nouveautés apportées par la nouvelle norme du C++ 11. Nous continuerons le sujet au cours du prochain chapitre et sans doute au cours du chapitre suivant. angel

@ bientôt pour le chapitre 7 : Les fonctions (2ème partie : pointeurs et références dans les fonctions)  

Gondulzak. 
   

 

Connexion

CoalaWeb Traffic

Today95
Yesterday232
This week1121
This month3414
Total1742621

20/04/24