Aller au contenu

Programmation C source/fonctions

Un livre de Wikilivres.
Programmation C source
Programmation C++
Programmation C++
Sommaire
Modifier ce modèle
#include <stdio.h>

int main(int argc, char * argv[])
{
	printf("----------------------------------------\n");
	printf(" Fonctions et procédures\n");
	printf("----------------------------------------\n");

	#if 0
	 type_retour fonction(type1 parametre1, type2 parametre2, /*...,*/ typeN parametreN)
	 {
		// Déclarations de variables
		// Instructions
	 }
	#endif

	/* L'exécution d'une fonction se termine soit lorsque l'accolade fermante est
	   atteinte, soit lorsque le mot clef return est rencontré. La valeur renvoyée par
	   une fonction est donnée comme paramètre à return. Une procédure est une fonction
	   renvoyant void, dans ce cas return est appelé sans paramètre. */

	/* Les passages des arguments aux fonctions se font toujours par valeur. Si on veut
	   modifier la valeur d'un argument passé en paramètre à une fonction, en dehors de
	   cette même fonction, il faut utiliser des pointeurs. */

	printf("\n----------------------------------------\n");
	printf(" Déclaration par prototype\n");
	printf("----------------------------------------\n");

	/* Le prototype d'une fonction correspond simplement à son en-tête (tout ce qui
	   précède la première accolade ouvrante). C'est-à-dire son nom, son type de retour
	   et les types des différents paramètres. Cela permet au compilateur de vérifier que
	   la fonction est appelée avec le bon nombre de paramètres et surtout avec les bons
	   types. La ligne suivante déclare la fonction fonction, mais sans la définir : */

	#if 0
	 type_retour nom_fonction(type1, type2, /* ..., */ typeN);
	#endif

	/* À noter que les noms des paramètres peuvent être omis et que la déclaration doit
	   se terminer par un point-virgule (;), sans quoi vous pourrez vous attendre à une
	   cascade d'erreurs. */

	printf("\n----------------------------------------\n");
	printf(" Absence des paramètres\n");
	printf("----------------------------------------\n");

	/* Avant la normalisation par l'ANSI, il était possible de faire une déclaration
	   partielle d'une fonction, en spécifiant son type de retour, mais pas ses
	   paramètres: */

	#if 0
	 int f();
	#endif

	/* Cette déclaration ne dit rien sur les éventuels paramètes de la fonction f, sur
	   leur nombre ou leur type, au contraire de : */

	#if 0
	 int g(void);
	#endif

	/* qui précise que la fonction g ne prend aucun argument.*/

	printf("\n----------------------------------------\n");
	printf(" Évaluation des arguments\n");
	printf("----------------------------------------\n");

	/* À noter que nulle part dans le langage n'est spécifié l'ordre d'évaluation des
	   arguments. Il faut donc faire attention aux opérateurs ayant des effets de bords,
	   notamment lorsqu'on utilise la même variable. Le code suivant est imprévisible et
	   non portable, bien qu'un compilateur ne génèrera probablement pas
	   d'avertissement : */

	#if 0
	 int fonction(int, int, int);

	 void test(void)
	 {
		int a = 0;
		int b = fonction(a++, a++, a++);
	 }
	#endif

	/* Suivant le compilateur employé, tous les cas de figures sont envisageables. La
	   fonction peut effectivement être appelée avec fonction(0, 0, 0)
	   ou fonction(0, 1, 2) ou encore fonction(2, 1, 0), ou le programme peut tout aussi
	   bien s'arrêter. */

	/* Un autre cas auquel il faut faire attention est l'appel de fonctions qui peuvent
	   avoir des effets de bord. Dans ce cas, le comportement n'est pas indéfini, mais
	   les fonctions peuvent être appelées dans n'importe quel ordre: */

	#if 0
	 int fonction(int, int, int);
	 int g(void);
	 int h(int);

	 void test(void)
	 {
		/* 3 appels de fonction en 1 instruction */
		int b = fonction(5, g(), h(6));
	 }
	#endif

	/* Dans cet exemple, g() peut être appelée avant h(6), ou l'inverse. Si ces
	   fonctions ont des effets de bord (affichage de messages, modification de
	   variables globales...), alors le comportement du programme dépendra de l'ordre
	   que le système choisit. Dans ce cas, la seule manière d'être sûr de l'ordre est
	   de l'imposer, de la manière suivante: */

	#if 0
	 int fonction(int, int, int);
	 int g(void);
	 int h(int);

	 void test(void)
	 {
		/* 1 instruction par appel de fonction */
		int a = g();
		int b = h(6);
		int c = fonction(5, a, b);
	 }
	#endif

	printf("\n----------------------------------------\n");
	printf(" Nombre variable d'arguments\n");
	printf("----------------------------------------\n");

	#if 0
	 #include <stdarg.h>
	 /* stdarg.h n'est nécessaire que pour traiter les arguments à l'intérieur de la
	   fonction */
 
	 void ma_fonction(type1 arg1, type2 arg2, ...)
	 {
	 }

	 /* Ceci n'est pas valide, il faut au moins un paramètre fixe */
	 void ma_fonction(...);
	#endif

	printf("\n----------------------------------------\n");
	printf(" Accès aux arguments\n");
	printf("----------------------------------------\n");

	#if 0
	 void va_start (va_list ap, last);
	 type va_arg (va_list ap, type);
	 void va_end (va_list ap);
	#endif

	/* va_list est un type opaque dont on n'a pas à se soucier. On commence par
	   l'initialiser avec va_start. Le paramètre last doit correspondre au nom du
	   dernier argument fixe de la fonction */

	/* il faut être extrêmement vigilant lors de la récupération des paramètres, à cause
	   de la promotion des types entiers ou réels. En effet, les entiers sont
	   systématiquement promus en int, sauf si la taille du type est plus grande, auquel
	   cas le type est inchangé. Pour les réels, le type float est promu en double,
	   alors que le type long double est inchangé. C'est pourquoi ce genre d'instruction
	   n'a aucun sens dans une fonction à nombre variable d'arguments : */

	#if 0
	 char caractere = va_arg(list, char);
	#endif

	/* Il faut obligatoirement récupérer un entier de type char, comme étant un entier
	   de type int. */

	#if 0
	 #include <stdio.h>
	 #include <stdarg.h>

	 enum { TYPE_FIN, TYPE_ENTIER, TYPE_REEL, TYPE_CHAINE };

	 void affiche(FILE * out, ...)
	 {
		va_list list;
		int type;
		va_start(list, out);
		while ((type = va_arg(list, int)))
		{
			switch (type)
			{
				case TYPE_ENTIER: fprintf(out, "%d", va_arg(list, int));
						  break;
				case TYPE_REEL:   fprintf(out, "%g", va_arg(list, double));
						  break;
				case TYPE_CHAINE: fprintf(out, "%s", va_arg(list, char *));
						  break;
			}
		}
		fprintf(out, "\n");
		va_end(list);
	 }

	 int main(int nb, char * argv[])
	 {
	  affiche(stdout, TYPE_CHAINE, "Le code ascii de 'a' est ", TYPE_ENTIER, 'a', 0);
	  affiche(stderr, TYPE_CHAINE, "2 * 3 / 5 = ", TYPE_REEL, 2. * 3 / 5, 0);

	  return 0;
	 }
	#endif

	/* À noter que la conversion (transtypage) explicite des types en une taille
	   inférieure à celle par défaut (int pour les entiers ou double pour les réels) ne
	   permet pas de contourner la promotion implicite. Même écrit de la sorte: */

	#if 0
	 affiche(stderr, TYPE_CHAINE, "2 * 3 / 5 = ", TYPE_REEL, (float) (2. * 3 / 5), 0);
	#endif

	/* Le résultat transmis au cinquième paramètre sera quand même promu implicitement
	   en type double. */

	printf("\n----------------------------------------\n");
	printf(" Fonction inline\n");
	printf("----------------------------------------\n");

	/* Il s'agit d'une extension ISO C99, qui à l'origine vient du C++. Ce mot clé doit
	   se placer avant le type de retour de la fonction. À noter qu'il est préférable de
	   classifier les fonctions inline de manière statique. Dans le cas contraire, la
	   fonction sera aussi déclarée comme étant accessible de l'extérieur, et donc
	   définie comme une fonction normale.

	   En la déclarant static inline, un bon compilateur devrait suprimer toute trace de
	   la fonction et seulement la mettre in extenso aux endroits où elle est utilisée.
	*/

	printf("\n----------------------------------------\n");
	printf(" La fonction main\n");
	printf("----------------------------------------\n");

	/* La norme définit deux prototypes, qui sont donc portables: */

	#if 0
	 int main(int argc, char * argv[]) { /* ... */ }
	 int main(void) { /* ... */ }
	#endif

	/* Le premier prototype est plus "général" : il permet de récupérer des paramètres
	   au programme. Le deuxième existe pour des raisons de simplicité, quand on ne veut
	   pas traiter ces arguments. */

	/* argc (argument count), est le nombre de paramètres qui ont été passés au
	   programme. argv (argument vector), est la liste de ces paramètres. Les paramètres
	   sont stockés sous forme de chaîne de caractères, argv est donc un tableau de
	   chaînes de caractères. argc correspond au nombre d'éléments de ce tableau. */

	/* La première chaîne de caractères, dont l'adresse est dans argv[0], contient le
	   nom du programme. Le premier paramètre est donc argv[1]. Le dernier élément du
	   tableau, argv[argc], est un pointeur nul. */

	printf("\n----------------------------------------\n");
	printf(" Valeur de retour\n");
	printf("----------------------------------------\n");

	/* La fonction main retourne toujours une valeur de type entier. L'usage veut qu'on
	   retourne 0 (ou EXIT_SUCCESS) si le programme s'est déroulé correctement, ou
	   EXIT_FAILURE pour indiquer qu'il y a eu une erreur (Les macros EXIT_SUCCESS et
	   EXIT_FAILURE étant définies dans l'en-tête stdlib.h). Il est possible par le
	   programme appelant de récupérer ce code de retour, et de l'interpréter comme bon
	   lui semble. */

	int i;

	for (i = 0; i < argc; i++)
		printf("paramètre %i : %s\n", i, argv[i]);

	printf("\n----------------------------------------\n");
	printf(" Fonctions en C pré-ANSI\n");
	printf("----------------------------------------\n");

	/* Avant la normalisation C89, on pouvait appeler une fonction sans disposer ni sa
	   définition, ni de sa déclaration. Dans ce cas, la fonction était implicitement
	   déclarée comme retournant le type int, et prenant un nombre indéterminé de
	   paramètres. */

	/* Aucune déclaration de g() n'est visible. */

	#if 0
	 void f(void)
	 {
		g(); /* Déclaration implicite: extern int g() */
	 }
	#endif

	/* À titre anecdoctique, ceci est la façon « historique » de définir une fonction,
	   avant que le prototypage ne fut utilisé. Cette notation est interdite depuis C90.
	*/

	#if 0
	 type_retour fonction(par1, par2, ..., parN)
	 type1 par1;
	 type2 par2;
	 ...
	 typeN parN;
	 {
		/* Déclarations de variables ... */
		/* Instructions ... */
	 }
	#endif

	/* Au lieu de déclarer les types à l'intérieur même de la fonction, ils sont
	   simplement décrits après la fonction et avant la première accolade ouvrante. À
	   noter que type_retour pouvait être omis, et dans ce cas valait par défaut int. */

	return 0;
}