Previous Contents Next

10   Fonctions

Définition d'une fonction : type   nom(liste des paramètres) { corps }

Exemple :

//=========================
int  max(int a,int b)
{ int res=b;
  if (a>b)  res = a;
  return res; 
}
//=========================
Appel d'une fonction : nom(liste des arguments)
La liste des arguments (aussi appelés paramètres réels) est de la forme: expr1, expr2, ...exprn où chaque expri est compatible avec le type typei du paramètre formel pi. Exemple :
//=========================
int k=34, t=5, m;
m = max(k,2*t+5);
//=========================
Il est possible de définir des fonctions récursives :
//=========================
int fact(int n)
{ if (n <= 1) 
   return 1;
  else return n*fact(n-1); }
//=========================
Un appel de fonction doit toujours être précédé par la définition ou une déclaration de la fonction. Une déclaration consiste à donner: son type, son nom et le type des paramètres (le nom des paramètres n'est pas nécessaire) suivi d'un point virgule :
Déclaration d'une fonction : type   nom(liste des types des paramètres);
Par exemple : ``int max(int ,int );''. Une déclaration permet au compilateur de vérifier que les différents appels sont corrects du point de vue du type des arguments et du type du résultat. A noter qu'une fonction doit être définie une et une seule fois mais peut être déclarée autant de fois qu'on le désire.

De plus, il est possible d'utiliser le même nom pour plusieurs fonctions si la liste des paramètres est différente (nombre ou type différent). Concernant le passage des paramètres, deux possibilités existent: le passage par valeur (standard) et le passage par référence.

10.1   Passage des paramètres par valeur.

C'est le cas standard: les paramètres sont initialisés par une copie des valeurs des paramètres réels. Modifier la valeur des paramètres formels dans le corps de la fonction ne change pas la valeur des paramètres réels. Par exemple, l'exécution du bout de programme à gauche avec la définition de proc à droite :

8cm
//=========================
int k=10;
proc(k);
cout<<"la valeur de k est "<<k;
//=========================
6cm
//=========================
void proc(int a)
{
a++;
cout<<"la valeur de a est "<<a;
cout << endl;
}
//=========================
entraînera l'affichage de ``la valeur de a est 11'' suivi d'un retour à la ligne puis de ``la valeur de k est 10''.

10.2   Passage des paramètres par référence.

Il est parfois nécessaire d'autoriser une fonction à modifier la valeur d'un paramètre réel. Pour cela, il est possible d'utiliser des références.

Cette notion de référence n'est pas limitée au passage de paramètres. De manière générale, une référence sur une variable est un synonyme de cette variable, c'est-à-dire une autre manière de désigner le même emplacement de la mémoire. On utilise le symbole & pour la déclaration d'une référence. Dans l'exemple ci-dessous :
int i = 4;
int & j = i;
nous avons déclaré j comme étant un synonyme de i: modifier j, c'est modifier i. La principale utilisation des références concernent le passage des paramètres. Dans la liste des paramètres formels d'une définition de fonction, ``type & pi'' déclare le paramètre pi comme étant une référence sur le ieme paramètre réel vi : pi n'est donc plus une variable créée pour l'exécution de la fonction et initialisée avec vi, mais pi est un synonyme de vi, modifier pi revient à modifier vi. Par exemple, l'exécution du bout de programme ci-dessous avec la définition de la procédure proc à droite :

8cm
//=========================
int k=10;
proc(k);
cout<<"la valeur de k est "<<k;
//=========================
6cm
//=========================
void proc(int & a)
{
a++;
cout<<"la valeur de a est "<<a<<endl;
}
//=========================
entraînera l'affichage de ``la valeur de a est 11'' suivi de ``la valeur de k est 11''.

Autre exemple :
//=========================
void permut(int & a, int & b)
{
int aux = a;
a = b;
b = aux;
}
//=========================
La fonction permut permet d'échanger la valeur des deux variables passées en argument. Il faut noter que le passage par référence demande de passer des variables (au sens large) en paramètres réels : on en peut pas appeler permut(i,3). On peut noter qu'il existe une fonction swap qui permute la valeur de deux arguments (de type quelconque) dans la bibliothèque algorithm (à utiliser avec #include <algorithm>).

Remarque 3  * En général, le passage par référence est plus efficace du point de vue de la gestion de la mémoire dans la mesure où il évite la recopie des arguments 4 (car seule l'adresse mémoire de l'argument est communiquée à la fonction) mais il est dangereux car des fonctions peuvent modifier les données. Il est donc quelquefois intéressant de passer des paramètres en tant que référence sur une constante (notation const type & p) : cela permet d'éviter le coût de la recopie des arguments et reste sûr car le compilateur vérifiera que le paramètre p n'est pas modifié dans le corps de la fonction. Cette technique est très largement utilisée et peut être vue comme une troisième manière de passer des arguments.
Pour finir, on peut noter qu'il est aussi possible pour une fonction de renvoyer comme résultat une référence.

10.3   Valeurs par défaut *

On peut attribuer des valeurs par défaut aux paramètres d'une fonction. Par exemple :
//=========================
int max(int a,int b =0)
{ 
  return (a>b) ? a : b;
} 
//=========================
On peut alors appeler max(i) au lieu de max(i,0). Il faut noter la restriction suivante : si un paramètre a une valeur par défaut, tous les paramètres suivants en ont une aussi.

10.4   Fonctions en ligne *

Pour de petites fonctions, il peut être plus efficace (rapide) de se dispenser d'un appel de fonction et d'expanser le corps de la fonction en lieu et place de chaque appel. C'est possible avec le mot clé inline. Par exemple, on pourra définir max de la sorte :
//=========================
inline int max(int a,int b =0)
{ 
  return (a>b) ? a : b;
} 
//=========================
Attention : ce choix n'est intéressant que pour de petites fonctions... A utiliser avec parcimonie !

10.5   Structure générale d'un programme C++

Un programme C++ est réparti dans un ou plusieurs fichiers, chacun contenant des définitions/déclarations de fonctions, des définitions de types (abordés plus loin) et des définitions de variables globales. Il existe une (seule) fonction main, elle correspond à la fonction qui sera exécutée après la compilation. Le profil de main est : int main(). Il est néanmoins possible de définir des paramètres à la fonction main afin d'exécuter le programme avec des arguments (voir un ouvrage de référence pour la syntaxe associée). Le programme ci-dessous est un programme complet avec deux fonctions :

//=========================
#include <iostream>

void test(int j)
{ cout << j << endl; }

int main() 
{
int i =20;
cout << "bonjour" << endl;
test(i);
}
//=========================
Remarque 4   * Un programme important utilise de nombreuses bibliothèques, fonctions etc. Pour éviter les problèmes de conflit de noms, on utilise des espaces de noms (namespace) : on associe un nom à un ensemble de variables, types, fonctions. Pour désigner ces différents objets, il faut alors utiliser leur nom précédé par leur nom d'espace suivi de ::. Par exemple, la fonction int fct(){...} définie dans le domaine A aura pour nom complet A::fct().
Pour les entrées-sorties avec
cout et cin, il faut en fait utiliser std::cout, std::cin et std::endl (i.e. les objets cout, cin et endl de la bibliothèque standard) pour être parfaitement correct (le compilateur tolère encore la notation simple mais...). C'est aussi le cas pour std::string et std:vector. Comme il est pénible de mentionner systématiquement le nom d'un espace, on peut utiliser la commande using pour préciser que les objets non-explicitement définis dans le code viennent de certains espaces de noms. Par exemple, tous nos programmes devraient (et doivent à partir de maintenant) comporter l'instruction suivante ``using namespace std;''.


Previous Contents Next