Comme dans la plupart des langages, on peut en C découper un programme en plusieurs fonctions. Une seule de ces fonctions existe obligatoirement ; c'est la fonction principale appelée main. Cette fonction principale peut, éventuellement, appeler une ou plusieurs fonctions secondaires. De même, chaque fonction secondaire peut appeler d'autres fonctions secondaires ou s'appeler elle-même (dans ce dernier cas, on dit que la fonction est récursive).
1. Définition d'une fonction
La définition d'une fonction est la donnée du texte de son algorithme, qu'on appelle corps de la fonction. Elle est de la forme
type nom-fonction (type-1 arg-1,...,type-n arg-n)
{[déclarations de variables locales ]
liste d'instructions
}
La première ligne de cette définition est l'en-tête de la fonction. Dans cet en-tête, type désigne le type de la fonction, c'est-à-dire le type de la valeur qu'elle retourne. Contrairement à d'autres langages, il n'y a pas en C de notion de procédure ou de sous-programme. Une fonction qui ne renvoie pas de valeur est une fonction dont le type est spécifié par le mot-clef void. Les arguments de la fonction sont appelés paramètres formels, par opposition aux paramètres effectifs qui sont les paramètres avec lesquels la fonction est effectivement appelée. Les paramètres formels peuvent être de n'importe quel type. Leurs identificateurs n'ont d'importance qu'à l'intérieur de la fonction. Enfin, si la fonction ne possède pas de paramètres, on remplace la liste de paramètres formels par le mot-clef void.
Le corps de la fonction débute éventuellement par des déclarations de variables, qui sont locales à cette fonction. Il se termine par l'instruction de retour à la fonction appelante, return, dont la syntaxe est
return(expression);
La valeur de expression est la valeur que retourne la fonction. Son type doit être le même que celui qui a été spécifié dans l'en-tête de la fonction. Si la fonction ne retourne pas de valeur (fonction de type void), sa définition s'achève par
return;
Plusieurs instructions return peuvent apparaître dans une fonction. Le retour au programme appelant sera alors provoqué par le premier return rencontré lors de l'exécution. Voici quelques exemples de définitions de fonctions :
int produit (int a, int b)
{
return(a*b);
}
int puissance (int a, int n)
{
if (n == 0)
return(1);
return(a * puissance(a, n-1));
}
void imprime_tab (int *tab, int nb_elements)
{
int i;
for (i = 0; i < nb_elements; i++)
printf("%d \t",tab[i]);
printf("\n");
return;
}
2. Appel d'une fonction
L'appel d'une fonction se fait par l'expression
nom-fonction(para-1,para-2,...,para-n)
L'ordre et le type des paramètres effectifs de la fonction doivent concorder avec ceux donnés dans l'en-tête de la fonction. Les paramètres effectifs peuvent être des expressions. La virgule qui sépare deux paramètres effectifs est un simple signe de ponctuation ; il ne s'agit pas de l'opérateur virgule. Cela implique en particulier que l'ordre d'évaluation des paramètres effectifs n'est pas assuré et dépend du compilateur. Il est donc déconseillé, pour une fonction à plusieurs paramètres, de faire figurer des opérateurs d'incrémentation ou de décrémentation (++ ou --) dans les expressions définissant les paramètres effectifs (cf. Chapitre 1).
3. Déclaration d'une fonction
Le C n'autorise pas les fonctions imbriquées. La définition d'une fonction secondaire doit donc être placée soit avant, soit après la fonction principale main. Toutefois, il est indispensable que le compilateur ``connaisse'' la fonction au moment où celle-ci est appelée. Si une fonction est définie après son premier appel (en particulier si sa définition est placée après la fonction main), elle doit impérativement être déclarée au préalable. Une fonction secondaire est déclarée par son prototype, qui donne le type de la fonction et celui de ses paramètres, sous la forme :
type nom-fonction(type-1,...,type-n);
Les fonctions secondaires peuvent être déclarées indifféremment avant ou au début de la fonction main. Par exemple, on écrira
int puissance (int, int );
int puissance (int a, int n)
{
if (n == 0)
return(1);
return(a * puissance(a, n-1));
}
main()
{
int a = 2, b = 5;
printf("%d\n", puissance(a,b));
}
Même si la déclaration est parfois facultative (par exemple quand les fonctions sont définies avant la fonction main et dans le bon ordre), elle seule permet au compilateur de vérifier que le nombre et le type des paramètres utilisés dans la définition concordent bien avec le protype. De plus, la présence d'une déclaration permet au compilateur de mettre en place d'éventuelles conversions des paramètres effectifs, lorsque la fonction est appelée avec des paramètres dont les types ne correspondent pas aux types indiqués dans le prototype. Ainsi les fichiers d'extension .h de la librairie standard (fichiers headers) contiennent notamment les prototypes des fonctions de la librairie standard. Par exemple, on trouve dans le fichier math.h le prototype de la fonction pow (élévation à la puissance) :
extern double pow(double , double );
La directive au préprocesseur
#include <math.h>
permet au préprocesseur d'inclure la déclaration de la fonction pow dans le fichier source. Ainsi, si cette fonction est appelée avec des paramètres de type int, ces paramètres seront convertis en double lors de la compilation.
Par contre, en l'absence de directive au préprocesseur, le compilateur ne peut effectuer la conversion de type. Dans ce cas, l'appel à la fonction pow avec des paramètres de type int peut produire un résultat faux !
5. Transmission des paramètres d'une fonction
Les paramètres d'une fonction sont traités de la même manière que les variables locales de classe automatique : lors de l'appel de la fonction, les paramètres effectifs sont copiés dans le segment de pile. La fonction travaille alors uniquement sur cette copie. Cette copie disparaît lors du retour au programme appelant. Cela implique en particulier que, si la fonction modifie la valeur d'un de ses paramètres, seule la copie sera modifiée ; la variable du programme appelant, elle, ne sera pas modifiée. On dit que les paramètres d'une fonction sont transmis par valeurs. Par exemple, le programme suivant
void echange (int, int );
void echange (int a, int b)
{
int t;
printf("debut fonction :\n a = %d \t b = %d\n",a,b);
t = a;
a = b;
b = t;
printf("fin fonction :\n a = %d \t b = %d\n",a,b);
return;
}
main()
{
int a = 2, b = 5;
printf("debut programme principal :\n a = %d \t b = %d\n",a,b);
echange(a,b);
printf("fin programme principal :\n a = %d \t b = %d\n",a,b);
}
imprime
debut programme principal : a = 2 b = 5 debut fonction : a = 2 b = 5 fin fonction : a = 5 b = 2 fin programme principal : a = 2 b = 5
Pour qu'une fonction modifie la valeur d'un de ses arguments, il faut qu'elle ait pour paramètre l'adresse de cet objet et non sa valeur. Par exemple, pour échanger les valeurs de deux variables, il faut écrire :
void echange (int *, int *);
void echange (int *adr_a, int *adr_b)
{
int t;
t = *adr_a;
*adr_a = *adr_b;
*adr_b = t;
return;
}
main()
{
int a = 2, b = 5;
printf("debut programme principal :\n a = %d \t b = %d\n",a,b);
echange(&a,&b);
printf("fin programme principal :\n a = %d \t b = %d\n",a,b);
}
Rappelons qu'un tableau est un pointeur (sur le premier élément du tableau). Lorsqu'un tableau est transmis comme paramètre à une fonction secondaire, ses éléments sont donc modifiés par la fonction. Par exemple, le programme
#include <stdlib.h>
void init (int *, int );
void init (int *tab, int n)
{
int i;
for (i = 0; i < n; i++)
tab[i] = i;
return;
}
main()
{
int i, n = 5;
int *tab;
tab = (int*)malloc(n * sizeof(int));
init(tab,n);
}
initialise les éléments du tableau tab.
7. La fonction main
La fonction principale main est une fonction comme les autres. Nous avons jusqu'à présent considéré qu'elle était de type void, ce qui est toléré par le compilateur. Toutefois l'écriture
main()
provoque un message d'avertissement lorsqu'on utilise l'option -Wall de gcc :
% gcc -Wall prog.c prog.c:5: warning: return-type defaults to `int' prog.c: In function `main': prog.c:11: warning: control reaches end of non-void function
En fait, la fonction main est de type int. Elle doit retourner un entier dont la valeur est transmise à l'environnement d'exécution. Cet entier indique si le programme s'est ou non déroulé sans erreur. La valeur de retour 0 correspond à une terminaison correcte, toute valeur de retour non nulle correspond à une terminaison sur une erreur. On peut utiliser comme valeur de retour les deux constantes symboliques EXIT_SUCCESS (égale à 0) et EXIT_FAILURE (égale à 1) définies dans stdlib.h. L'instruction return(statut); dans la fonction main, où statut est un entier spécifiant le type de terminaison du programme, peut être remplacée par un appel à la fonction exit de la librairie standard (stdlib.h). La fonction exit, de prototype
void exit(int statut);
provoque une terminaison normale du programme en notifiant un succès ou un échec selon la valeur de l'entier statut.
Lorsqu'elle est utilisée sans arguments, la fonction main a donc pour prototype
int main(void);
On s'attachera désormais dans les programmes à respecter ce prototype et à spécifier les valeurs de retour de main.
La fonction main peut également posséder des paramètres formels. En effet, un programme C peut recevoir une liste d'arguments au lancement de son exécution. La ligne de commande qui sert à lancer le programme est, dans ce cas, composée du nom du fichier exécutable suivi par des paramètres. La fonction main reçoit tous ces éléments de la part de l'interpréteur de commandes. En fait, la fonction main possède deux paramètres formels, appelés par convention argc (argument count) et argv(argument vector). argc est une variable de type int dont la valeur est égale au nombre de mots composant la ligne de commande (y compris le nom de l'exécutable). Elle est donc égale au nombre de paramètres effectifs de la fonction + 1. argv est un tableau de chaînes de caractères correspondant chacune à un mot de la ligne de commande. Le premier élément argv[0] contient donc le nom de la commande (du fichier exécutable), le second argv[1] contient le premier paramètre....
Le second prototype valide de la fonction main est donc
int main ( int argc, char *argv[]);
Ainsi, le programme suivant calcule le produit de deux entiers, entrés en arguments de l'exécutable :
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int a, b;
if (argc != 3)
{
printf("\nErreur : nombre invalide d'arguments");
printf("\nUsage: %s int int\n",argv[0]);
return(EXIT_FAILURE);
}
a = atoi(argv[1]);
b = atoi(argv[2]);
printf("\nLe produit de %d par %d vaut : %d\n", a, b, a * b);
return(EXIT_SUCCESS);
}
On lance donc l'exécutable avec deux paramètres entiers, par exemple,
a.out 12 8
Ici, argv sera un tableau de 3 chaînes de caractères argv[0], argv[1] et argv[2] qui, dans notre exemple, valent respectivement "a.out", "12" et "8". Enfin, la fonction de la librairie standard atoi(), déclarée dans stdlib.h, prend en argument une chaîne de caractères et retourne l'entier dont elle est l'écriture décimale.
9. Fonctions avec un nombre variable de paramètres
Il est possible en C de définir des fonctions qui ont un nombre variable de paramètres. En pratique, il existe souvent des méthodes plus simples pour gérer ce type de problème : toutefois, cette fonctionnalité est indispensable dans certains cas, notamment pour les fonctions printf et scanf.
Une fonction possédant un nombre variable de paramètre doit posséder au moins un paramètre formel fixe. La notation ...(obligatoirement à la fin de la liste des paramètres d'une fonction) spécifie que la fonction possède un nombre quelconque de paramètres (éventuellement de types différents) en plus des paramètres formels fixes. Ainsi, une fonction ayant pour prototype
int f(int a, char c, ...);
prend comme paramètre un entier, un caractère et un nombre quelconque d'autres paramètres. De même le prototype de la fonction printf est
int printf(char *format, ...);
puisque printf a pour argument une chaîne de caractères spécifiant le format des données à imprimer, et un nombre quelconque d'autres arguments qui peuvent être de types différents.
Un appel à une fonction ayant un nombre variable de paramètres s'effectue comme un appel à n'importe quelle autre fonction.
Pour accéder à la liste des paramètres de l'appel, on utilise les macros définies dans le fichier en-tête stdarg.h de la librairie standard. Il faut tout d'abord déclarer dans le corps de la fonction une variable pointant sur la liste des paramètres de l'appel ; cette variable a pour type va_list. Par exemple,
va_list liste_parametres;
Cette variable est tout d'abord initialisée à l'aide de la macro va_start, dont la syntaxe est
va_start(liste_parametres, dernier_parametre);
où dernier_parametre désigne l'identificateur du dernier paramètre formel fixe de la fonction. Après traitement des paramètres, on libère la liste à l'aide de la va_end :
va_end(liste_parametres);
On accède aux différents paramètres de liste par la macro va_arg qui retourne le paramètre suivant de la liste:
va_arg(liste_parametres, type)
où type est le type supposé du paramètre auquel on accède.
Notons que l'utilisateur doit lui-même gérer le nombre de paramètres de la liste. Pour cela, on utilise généralement un paramètre formel qui correspond au nombre de paramètres de la liste, ou une valeur particulière qui indique la fin de la liste.
Cette méthode est utilisée dans le programme suivant, où la fonction add effectue la somme de ses paramètres en nombre quelconque.
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
int add(int,...);
int add(int nb,...)
{
int res = 0;
int i;
va_list liste_parametres;
va_start(liste_parametres, nb);
for (i = 0; i < nb; i++)
res += va_arg(liste_parametres, int);
va_end(liste_parametres);
return(res);
}
int main(void)
{
printf("\n %d", add(4,10,2,8,5));
printf("\n %d\n", add(6,10,15,5,2,8,10));
return(EXIT_SUCCESS);
}
2017-10-15 16:08:03 / mazoughou@magoe.gn
0 commentaires
Votre impression compte aussi