Langage C:Fonctions
Un article de WikiTuto.
Sommaire |
Définitions générales des fonctions
Une fonction est définie par son entête, suivie d'un bloc d'instructions
entête
type_retourné nom_fonction(liste_arguments) (pas de ;)
Avant la norme ANSI, le type_retourné pouvait être omis si int. Désormais il est obligatoire, si la fonction ne retourne rien on indique : void.
La liste_arguments doit être typée (ANSI), alors qu'auparavant les types étaient précisés entre l'entête et le bloc :
ANSI: float truc(int a, float b) {bloc}
K&R: float truc(a,b) int a;float b; {bloc}
Si la fonction n'utilise pas d'arguments il faut la déclarer (ANSI) nom(void) ou (K&R) nom(). L'appel se fera dans les deux cas par nom() (parenthèses obligatoires).
Les arguments (formels) sont des variables locales à la fonction. Les valeurs fournies à l'appel de la fonction (arguments réels) y sont recopiés à l'entrée dans la fonction. Les instructions de la fonction s'exécutent du début du bloc ({) jusqu'à return(valeur) ou la sortie du bloc (}). La valeur retournée par la fonction est indiquée en argument de return.
exemples
- fonction ne recevant aucun argument, et ne retournant rien. Ce n'est pas parce qu'elle ne retourne rien qu'elle ne fait rien, ici elle affiche un message à l'écran. Elle ne reçoit aucun argument, ici parce qu'elle n'en a pas besoin, quelquefois c'est parce qu'elle n'utilise que des variables globales (chose que je ne conseille pas).
void message(void)
{
int i;
for(i=0;i<10;i++)printf("bonjour monde\n");
}
On appelle cette fonction par message(); Une fonction possède ses propres variables locales (ici i), mais n'a pas accès aux variables locales des autres fonctions, pas même celles de main (mais aux variables globales, que je déconseille fortement au débutant)
- fonction recevant un (ou plusieurs) argument(s), et ne retournant rien. Une telle fonction a besoin d'informations pour effectuer sa tâche, ce qui est le cas le plus courant.
void insiste(int combien_de_fois,char * compliment)
{
int i;
printf("Oh que tu es ");
for(i=0;i<combien_de_fois;i++)printf("%s ",compliment);
printf("!!!!! \n);
}
On appelle cette fonction par exemple par insiste(10,"belle"); ou int j=3; char i[]="fatiguant"; insiste(j*2,i); Tout ce qui importe à l'appel est qu'on donne en premier argument une valeur entière et en second une chaîne de caractères. Leurs noms n'ont aucun rapport avec ceux utilisés dans la fonction.
- fonction recevant un (ou plusieurs, ou aucun) argument(s), et retournant une valeur. Une telle fonction a besoin (ou non) d'informations pour effectuer sa tâche, mais quand la tâche est terminée (en général c'était un calcul), elle rend (retourne) le résultat (une et une seule valeur). Je vous rapelle que les variables locales de la fonction ne sont pas visibles à l'extérieur de la fonction, sans cela les calculs auraient été inutiles.
float produit(float a;float b)
{
float z;
z=a*b;
return(z);
}
On appelle cette fonction dans une expression nécessitant un float : x=produit(2,4.5); ou y=2+produit(sin(x)+produit(1.12,X),produit(2,2)) ou printf("le double de %f est %f\n",z,produit(z,2));
- que fait-on pour retourner plusieurs valeurs ? C'est impossible directement. On peut retourner un pointeur (voir plus loin) qui permet de retourner l'adresse d'un ensemble de valeurs (tableau ou structure). On peut aussi transmettre des "récipients" pour les valeurs résultantes, gràce au passage d'arguments par adresse (voir plus loin). On pourait aussi utiliser des variables globales, mais ce qui prouve que vous êtes un programmeur expérimenté est que vous savez que c'est une très mauvaise solution.
Récursivité, gestion de la pile
Une fonction peut s'appeler elle-même :
int factorielle(int i)
{
if (i>1) return(i*factorielle(i-1));
else return(1);
}
analysons la pile en appelant factorielle(3) :
| i=1 | ||||
| i=2 | i=2 | i=2 | ||
| i=3 | i=3 | i=3 | i=3 | i=3 |
| (a) | (b) | (c) | (d) | (e) |
- (a) appel de factorielle(3), création de i, à qui on affecte la valeur 3. comme i>1 on calcule i*factorielle(i-1) : i=3,i-1=2 on appelle factorielle(2)
- (b) création i, affecté de la valeur 2, i>1 donc on appelle factorielle(1)
- (c) création de i, i=1 donc on quitte la fonction, on libère le pile de son sommet, on retourne où la fonction factorielle(1) a été appelée en rendant 1.
- (d) on peut maintenant calculer i*factorielle(1), i (sommet de la pile) vaut 2, factorielle(1) vaut 1, on peut rendre 2, puis on "dépile" i
- (e) on peut calculer i*factorielle(2), i vaut 3 (sommet de la pile), factorielle(2) vaut 2, 3*2=6, on retourne 6, la pile est vidée et retrouve sont état initial.
Attention, la récursivité est gourmande en temps et mémoire, il ne faut l'utiliser que si l'on ne sait pas facilement faire autrement :
int factorielle(int i)
{
int result;
for(result=1;i>1;i--) result*=i;
return(result);
}
Arguments passés par adresse
Imaginons la fonction :
void échange(int i;int j)
{int k;k=i;i=j;j=k;}
Lors d'un appel à cette fonction par échange(x,y), les variables locales i,j,k sont créées sur la pile, i vaut la valeur de x, j celle de y. Les contenus de i et j sont échangés puis la pile est libérée, sans modifier x et y. Pour résoudre ce problème, il faut passer par des pointeurs. On utilisera les opérateurs unaires : & (adresse de) et * (contenu de). Définissons donc la fonction ainsi :
void échange(int *i;int *j)
{int k;k=*i;*i=*j;*j=k;}
On appelle la fonction par échange(&x,&y); les deux arguments formels de la fonction (i et j) sont des pointeurs sur des int, c'est à dire qu'à l'appel, on crée sur la pile des variables i et j pouvant contenir une adresse, dans i on recopie l'argument réel qui et l'adresse de x, et l'adresse de y dans j. en entrant dans le bloc, on crée une variable locale k pouvant contenir un entier. Puis on met dans k non pas la valeur de i mais le contenu pointé par i (donc ce qui se trouve à l'adresse marquée dans i, qui est l'adresse de x), donc le contenu de x. On place la valeur pointée par j (donc y) à l'adresse pointée par j (donc x). Puis on place la valeur de k à l'adresse pointée par j (y). On a donc échangé x et y.
On a tout intérêt à essayer cet exemple en se fixant des adresses et des valeurs, et voir l'évolution des contenus des variables.
En conclusion, pour effectuer un passage d'argument par adresse, il suffit d'ajouter l'opérateur & devant l'argument réel (à l'appel de la fonction), et l'opérateur * devant chaque apparition de l'argument formel, aussi bien dans l'entête que le bloc de la fonction.
Fonction main
Si on le désire, la fonction main peut rendre un entier (non signé) au système d'exploitation (sous MSDOS, on récupère cette valeur par ERRORLEVEL). Une valeur 0 signale en général une fin normale du programme, sinon elle représente un numéro d'erreur. L'arrivée sur le } final retourne la valeur 0, dans le cas où on n'a pas indiqué de return(code).
De même, le système d'exploitation peut transmettre des arguments au programme. La déclaration complète de l'entête de la fonction main est :
int main(int argc,char *argv[], char *env[])
Le dernier argument est optionnel.
On peut aussi utiliser char **argv, mais cela peut paraître moins clair. argc indique le nombre de mots de la ligne de commande du système d'exploitation, argv est un tableau de pointeurs sur chaque mot de la ligne de commande, env pointe sur les variables de l'environnement (sous MSDOS obtenues par SET, sous UNIX par env).
Si votre programme s'appelle COPIER, et que sous votre système vous ayez entré la commande COPIER TRUC MACHIN alors argc vaut 3, argv[0] pointe sur "COPIER", argv[1] sur "TRUC" et argv[2] sur "MACHIN". argv[3] vaut le pointeur NULL. env est un tableau de pointeurs sur les variables d'environnement, on n'en connaît pas le nombre mais le dernier vaut le pointeur NULL.
Fonction retournant un pointeur et pointeur de fonction
type*fonc(arguments) est une fonction qui renvoie un pointeur.
exemple
int *max(int tableau[], int taille)
{
int i,*grand;
for(grand=tableau,i=1;i<taille;i++)
if(tableau[i]>*grand) grand=tableau+i;
return(grand);
}
Cette fonction rend l'adresse du plus grand entier du tableau.
type (*fonc)(arguments) est un pointeur sur une fonction
exemple
int max(int,int);
int min(int,int);
int main(void);
{
int (*calcul)(int,int);
/* calcul est un pointeur donc une variable qui
peut être locale */
char c;
puts("utiliser max (A) ou min (I) ?");
do c=getchar(); while ((c!='A')&&(c!='I'));
calcul=(c=='A')?&max:&min;
printf("%d\n",(*calcul)(10,20));
}
int max(int a,int b) {return(a>b?a:b);}
int min(int a,int b) {return(a<b?a:b);}
Cette fonctionnalité du C est assez peu utilisée, mais est nécessaire dans les langages orientés objets.
Voir aussi
- Instructions
- Structures de contrôle
- Déclaration et stockage des variables
- Fonctions
- Variables scalaires
- Tableaux
- Structures et unions
Auteur :Patrick TRAU. Copyright : utilisation de ces documents libre pour tout usage personnel. Utilisation autorisée pour tout usage public non commercial, à condition de citer son auteur (Patrick TRAU, IPST, Université Louis Pasteur Strasbourg, email : Patrick.Trau (à) ipst-ulp.u-strasbg.fr ) et de me signaler tout usage intensif. Utilisation commerciale interdite sans accord écrit de ma part.



