📘 Le cours : structurer un programme en fonctions
Une fonction regroupe un morceau de code nommé et réutilisable. Découper un programme en petites fonctions (une idée par fonction) le rend plus lisible, plus facile à tester et à corriger. Tu en utilises déjà : printf, scanf et main sont des fonctions.
Anatomie d'une fonction
type_de_retour nom(type param1, type param2)
{
/* corps de la fonction */
return valeur; /* du type annoncé */
}
- Le type de retour dit ce que la fonction renvoie ;
void signifie « rien ».
- Les paramètres sont des variables locales initialisées avec les valeurs de l'appel (les arguments).
return termine la fonction immédiatement, même au milieu d'une boucle.
- Écris
void f(void) et non void f() : en C, des parenthèses vides veulent dire « paramètres non précisés ».
Prototype et définition
Le compilateur lit le fichier de haut en bas : une fonction doit être connue avant d'être appelée. Deux solutions : la définir avant main, ou écrire un prototype (la première ligne, terminée par ;) en haut du fichier et donner la définition plus loin, comme dans l'exemple de la première carte.
Fonction sans résultat
Une fonction void agit (elle affiche, modifie, enregistre…) sans renvoyer de valeur.
#include <stdio.h>
void barre(int longueur) {
for (int i = 0; i < longueur; i++) {
printf("=");
}
printf("\n");
}
int main(void) {
barre(10);
printf("NSI\n");
barre(10);
return 0;
}
Affichage==========
NSI
==========
Passage par valeur
En C, tout est passé par valeur : la fonction reçoit une copie de chaque argument. Modifier le paramètre ne change donc pas la variable de l'appelant. Pour la modifier, on passe son adresse avec & et la fonction écrit à cette adresse avec *.
#include <stdio.h>
void ajoute_un(int n) {
n = n + 1; /* modifie la copie locale */
}
void ajoute_un_adr(int *n) {
*n = *n + 1; /* modifie la variable de l'appelant */
}
int main(void) {
int a = 5;
ajoute_un(a);
printf("%d\n", a); /* inchangé */
ajoute_un_adr(&a);
printf("%d\n", a); /* augmenté de 1 */
return 0;
}
Renvoyer plusieurs résultats
Une fonction ne renvoie qu'une seule valeur. Pour en produire plusieurs, on passe des adresses dans lesquelles elle écrit ses résultats.
#include <stdio.h>
void extremes(const int t[], int n, int *min, int *max) {
*min = t[0];
*max = t[0];
for (int i = 1; i < n; i++) {
if (t[i] < *min) *min = t[i];
if (t[i] > *max) *max = t[i];
}
}
int main(void) {
int notes[] = {12, 7, 15, 9};
int petit, grand;
extremes(notes, 4, &petit, &grand);
printf("min = %d, max = %d\n", petit, grand);
return 0;
}
Affichagemin = 7, max = 15
Portée des variables
- Une variable locale (dans une fonction ou un bloc
{ }) n'existe que dans ce bloc : elle disparaît à la sortie.
- Une variable globale (hors de toute fonction) est visible partout. À éviter : n'importe quelle fonction peut la modifier, ce qui rend les bugs difficiles à trouver.
- Une variable locale
static est créée une seule fois et garde sa valeur entre deux appels.
#include <stdio.h>
int compteur(void) {
static int appels = 0; /* initialisée une seule fois */
appels++;
return appels;
}
int main(void) {
printf("%d\n", compteur());
printf("%d\n", compteur());
printf("%d\n", compteur());
return 0;
}
Récursivité
Une fonction récursive s'appelle elle-même. Elle a besoin d'un cas de base qui arrête la récursion et d'un appel sur un cas plus petit. Sans cas de base, les appels s'empilent jusqu'au dépassement de la pile (*stack overflow*) et le programme plante.
#include <stdio.h>
unsigned long long fact(unsigned int n) {
if (n <= 1) {
return 1; /* cas de base */
}
return n * fact(n - 1); /* appel sur un cas plus petit */
}
int main(void) {
printf("5! = %llu\n", fact(5));
printf("20! = %llu\n", fact(20));
return 0;
}
Affichage5! = 120
20! = 2432902008176640000
Au-delà de 20!, le résultat dépasse ce qu'un unsigned long long peut contenir et devient faux : c'est le dépassement de capacité vu dans Entiers signés.
Découper en plusieurs fichiers
Dans un vrai projet, les prototypes sont regroupés dans un fichier d'en-tête .h et les définitions dans un .c. Le garde d'inclusion (#ifndef / #define / #endif) évite d'inclure deux fois le même en-tête.
#ifndef UTILS_H
#define UTILS_H
int carre(int x);
int max2(int a, int b);
#endif
On l'inclut avec des guillemets : #include "utils.h" (fichier du projet) alors que #include <stdio.h> désigne une bibliothèque du système. Compilation : gcc main.c utils.c -o main.