
FAQ CConsultez toutes les FAQ
Nombre d'auteurs : 28, nombre de questions : 175, création le 11 janvier 2013
Sommaire→Les fonctions et les variables globales→Les fonctions- Qu'est-ce qu'un prototype ?
- Est-il possible de définir une fonction sans fournir le prototype ?
- Comment passer 'une variable' à une fonction ?
- Comment créer une fonction qui retourne plus d'une valeur ?
- Comment créer une fonction qui retourne une chaîne de caractères ?
- Comment retourner un pointeur de fonction ?
- Comment définir une fonction acceptant un nombre d'arguments variable, comme printf() ?
- Combiner plusieurs options en un seul paramètre ?
Un prototype est la partie qui, dans la définition d'une fonction, définit les relations entre la fonction le monde extérieur. Par exemple dans :
double f(double x, int n)
{
...
}
Toute la première ligne constitue le prototype de la fonction. Elle indique :
- Le nom de la fonction : f
- Le type de la valeur retournée : double
- Le nombre de paramètres requis : 2 dont le premier de type double (x) et le deuxième de type int (n)
Une fonction avant d'être utilisée doit être déclarée. La déclaration permet au compilateur de s'assurer que la fonction sera toujours correctement utilisée dans le programme. Pour déclarer une fonction, il suffit de prendre son prototype puis d'ajouter un point-virgule à la fin.
Lien : Est-il possible de définir une fonction sans fournir le prototype ?
Oui, et c'était d'ailleurs le seul moyen de définir une fonction avant la normalisation du langage C, c'est-à-dire dans le C originel (K & R C). Voici un exemple de définition d'une fonction selon cette méthode :
double f(x, n)
double x; int n;
{
...
}
Et voici comment on devait déclarer la fonction :
double f(); /* Les parentheses, toujours vides, qui suivent f servent a indiquer que f est une fonction. */
/* Sans elles, la ligne definirait plutot une variable globale de type double nommee f. */
Ces méthodes ne permettent pas au compilateur de contrôler la validité des paramètres passées à une fonction à chaque appel à cause du manque de renseignement qu'elles lui fournissent d'où l'invention du prototype par l'ANSI.
Lien : Qu'est-ce qu'un prototype ?
Supposons que l'on veut écrire une fonction, reset, qui permette de mettre à 0 une variable de type int. Faisons donc :
void reset(int n)
{
n = 0;
}
L'extrait de code suivant :
int a = 10;
reset(a);
Ne met pas a à 0 (après l'appel reset(a), on aura donc toujours "a = 10" !) car reset(a) ne fait que passer la valeur de a (ici 10) à la fonction reset qui copie ensuite
cette valeur dans sa variable locale n puis met cette variable à 0 ! C'est donc n que la fonction modifie et non a !
Pour que la fonction reset puisse modifier la valeur de "la variable" qu'on lui a transmise, ce n'est pas la valeur de la variable qu'il faut lui fournir mais plutôt son adresse.
Connaissant l'adresse de la variable, la fonction peut aussi bien lire que modifier sa valeur. Le bon code est donc :
#include <stdio.h>
void reset(int * adr);
int main(void)
{
int a = 10;
reset(&a);
printf("a = %d\n", a); /* affiche "a = 0" */
return 0;
}
void reset(int * adr)
{
*adr = 0;
}
Une fonction ne peut retourner au plus qu'une valeur, mais il est possible de créer une fonction qui retourne d'autres valeurs via un ou plusieurs paramètres. Par exemple :
#include <stdio.h>
#include <math.h>
void decomposer_nombre(double x, double * ptr1, double * ptr2);
int main(void)
{
double x, e, d;
printf("x = ");
scanf("%lf", &x); /* Supposons que l'utilisateur tape 2.5 */
decomposer_nombre(x, &e, &d); /* On donne une entree (x), on obtient deux sorties (e et d) */
printf("e = %f\n", e); /* Pour x = 2.5, affiche e = 2.000000 */
printf("d = %f\n", d); /* Pour x = 2.5, affiche d = 0.500000 */
return 0;
}
void decomposer_nombre(double x, double * ptr1, double * ptr2)
{
/* Nous allons mettre dans *ptr1 la partie entiere de x */
/* et dans *ptr2 sa partie decimale */
*ptr1 = floor(x);
*ptr2 = x - *ptr1;
}
Cette fonction retourne deux valeurs, un indice de réussite (le retour de la fonction) et une valeur entière.
Une première méthode consiste à passer à la fonction un pointeur vers la zone mémoire destinée à contenir la chaîne à retourner. Par exemple :
#include <stdio.h>
#include <string.h>
void get_string(char * Buffer, size_t BufferLen)
{
/* On ne peut pas utiliser strcpy car cette fonction
ne permet pas d'indiquer le nombre max de caracteres qu'on veut copier */
strncpy(Buffer, "MaChaine", BufferLen);
/* Il est possible que Buffer n'ait pas pu contenir toute la chaine ... */
Buffer[BufferLen - 1] = '\0';
}
int main(void)
{
char Buffer[100];
get_string(Buffer, sizeof(Buffer));
printf("%s\n", Buffer);
return 0;
}
Une autre façon de faire est de retourner un pointeur vers des caractères situées en mémoire globale. Par exemple :
- A l'aide d'une chaîne statique :
#include <stdio.h>
const char * get_string(void)
{
return "MaChaine";
}
int main(void)
{
printf("%s\n", get_string());
return 0;
}
- A l'aide d'une chaîne allouée dynamiquement :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * alloc_string(void)
{
char * Buffer, s[] = "MaChaine";
Buffer = malloc(strlen(s) + 1);
if (Buffer != NULL)
strcpy(Buffer, s);
return Buffer;
}
int main(void)
{
char * Buffer = alloc_string();
if (Buffer != NULL)
{
printf("%s\n", Buffer);
free(Buffer);
}
return 0;
}
Voici un exemple de fonction, func, ne prenant aucun argument et qui retourne un pointeur sur une fonction de type int f(const char * s) (par exemple puts) :
int (*func(void))(const char *)
{
return puts;
}
Le prototype de func qui paraît pour le moins ésotérique a en fait été obtenu par la méthode suivante : func retournant un pointeur, func(void)
vaut ce pointeur et *func(void) la chose pointée. Cette chose pointée est une fonction qui prend en argument un const char * et qui retourne un int donc
(*func(void))(const char *) est de type int d'où le prototype de f : int (*func(void))(const char *).
Bien entendu, ici encore un typedef allège énormément l'écriture.
#include <stdio.h>
typedef int F(const char *);
F * func(void);
int main(void)
{
func()("Bonjour."); /* <=> puts("Bonjour."); */
return 0;
}
F * func(void)
{
return puts;
}
Les '...' dans la liste des paramètres d'une fonction indiquent que la fonction peut accepter 0, un ou plusieurs paramètres à la place. Parmi les paramètres de la fonction, il doit cependant y a voir au moins un qui explicite (fixé). Les macros va_start(), va_arg() et va_end() définies dans stdarg.h permettent de parcourir les paramètres. Dans l'exemple ci-dessous, on parcourt les paramètres jusqu'à en rencontrer un qui vaut 0.
#include <stdio.h>
#include <stdarg.h>
void printf_total(const char * format, ...);
int main(void)
{
print_total("Le total est : %d\n", 1, 2, 3, 4, 0);
return 0;
}
/* Fonction print_total : affiche la somme des arguments */
void printf_total(const char * format, ...)
{
int total = 0;
va_list ap;
int arg;
va_start(ap, format);
while ((arg = va_arg(ap, int)) != 0)
total += arg;
printf(format, total);
va_end(ap);
}
Le système présenté ici permet de fournir à une fonction une valeur qui regroupe un ensemble de flags (drapeaux) souvent utilisés pour permettre à l'utilisateur de fournir un nombre variable d'options sans utiliser le système de fonction à nombre variable d'arguments (va_list). Pour cela, il faut commencer par définir les différentes options possibles :
enum {
OPTION1 = 1 << 0,
OPTION2 = 1 << 1
};
Notez que les valeurs des options doivent être des puissances de deux pour avoir un bit égal à 1 et les autres égals à 0. D'après l'énumération précédente, OPTION1 vaut 1 soit 000001 en binaire et OPTION2 vaut 000010, alors pour passer les deux paramètres à une fonction dont le prototype serait le suivant :
void foo(int options)
on écrit :
foo(OPTION1 | OPTION2)
D'après les propriétés du OU binaire, la fonction va recevoir la valeur 000011. Ensuite, il va falloir décomposer cette valeur pour retrouver les différentes options. Il faut faire l'opération inverse du OU binaire grâce au ET binaire :
options & OPTION1
La valeur de cette expression est différente de 0 (soit vrai) si l'un des bits de OPTIONS1 se retrouve dans options. Il faut faire le test pour chacune des options proposées. Voici un code complet :
#include <stdio.h>
enum {
OPTION1 = 1 << 0,
OPTION2 = 1 << 1
};
void foo(int);
int main(void)
{
foo(OPTION1 | OPTION2);
return 0;
}
void foo(int options)
{
if (options & OPTION1)
printf("OPTION1 trouve\n");
if (options & OPTION2)
printf("OPTION2 trouve\n");
}



