Fonctions

Nous savons pour l'instant écrire un appel de fonction pour en déclencher l'exécution, mais nous n'avons utilisé que des fonctions fournies par la bibliothèque standard. Nous allons à présent voir comment définir nos propres fonctions.

int somme(int n, int m) {
  return n+m;
}
 
int main(void) {
  printf("%d\n", somme(12, 45));
  return EXIT_SUCCESS;
}

Comme pour les autres formes de déclarations, la définition d'une fonction doit précéder son utilisation. Il est souhaitable dans la majorité des cas de placer la déclaration au niveau global, c'est à dire en dehors de la fonction principale.

La déclaration d'une fonction est formée de deux parties : la signature et le corps. Le corps est simplement un bloc contenant les instructions à exécuter à chaque invocation de la fonction.

La signature commence toujours par le nom d'un type : c'est le type de la valeur qui sera produite par l'exécution de la fonction (pour faire court, on parle de type de retour). Pour terminer proprement l'exécution d'une fonction, il faut placer dans le corps de la fonction une instruction return contenant une expression dont le résultat donne la valeur de retour.

Remarque Il est possible qu'une fonction ne produise pas de valeur de retour. Dans ce cas, le type de retour est noté void. Vous pouvez alors terminer l'exécution d'une telle fonction par une instruction return ne contenant pas d'expression, ou en atteignant la fin du bloc sans rencontrer aucune instruction return. Une telle fonction est souvent appelée une procédure.

Après le type de retour, la signature contient le nom de la fonction. Le choix de l'identificateur d'une fonction obéit aux mêmes règles que pour une variable.

Après le nom et entre parenthèses, on trouve la liste des paramètres : des déclarations de variables (sans initialisation) séparées par des virgules. Les paramètres sont des variables dont la portée est le corps de la fonction. Lors de l'appel d'une fonction, les arguments d'appel servent à initialiser les paramètres.

Remarque Toutes les variables utilisées par une fonction ne sont pas nécessairement des paramètres ! Vous pouvez également déclarer des variables dans le corps. N'employez des paramètres que si la fonction a besoin de recevoir de l'information venant d'une autre partie du programme.

On doit toujours placer la déclaration d'une fonction avant son utilisation. Mais toute la déclaration n'est pas nécessaire. On peut employer une forme simplifiée nommée déclaration préalable ou prototype. Dans cette forme, le corps est remplacé par un point-virgule et le nom des paramètres peut être omis.

int somme(int, int);

Bien sûr, la définition complète devra être fournie à un moment ou à un autre : plus loin dans la suite du code, ou même dans un tout autre fichier. C'est la raison pour laquelle les fichiers d'en-tête vous donnent accès aux fonctions de la bibliothèque standard : ils contiennent les prototypes nécessaires (mais pas les définitions complètes).

Pour un humain aussi, le prototype est une source précieuse d'information. On peut y voir le nombre et le type des paramètres, ce qui est indispensable pour choisir les arguments d'appel. On y trouve également le type de retour, ce qui permet de savoir comment utiliser le résultat de l'appel. C'est pourquoi les pages manuelles s'appuient sur le prototype pour leurs explications.

Lorsqu'un tableau est passé en paramètre d'une fonction, c'est l'adresse qui est effectivement transmise.

float moyenne(const float notes[][15], int semestres, int modules) {
  float somme = 0.0f;
  int s, m;
 
  for(s = 0; s < semestres; s++) {
    for(m = 0; m < modules; m++) {
      somme += notes[s][m];
    }
  }
 
  return somme/(float) (semestres*modules);
}
 
int main(void) {
  float m, tab[4][15];
  ...
  m = moyenne(tab, 2, 10);
  ...
}

Le paramètre est défini comme un tableau ordinaire, sauf que la capacité de la première dimension peut être omise. Il recoit l'adresse d'un tableau préexistant, donc ce n'est pas un nouveau tableau !

Puisqu'il s'agit d'un passage par adresse, les éléments du tableau peuvent être modifiés par la fonction. Pour indiquer qu'aucune modification n'est envisagée, le paramètre doit comporter le qualificatif const.

  1. Découpage. Reprenez le programme du deuxième exercice du troisième sujet sur les boucles. Définissez une fonction qui affiche un triangle, une fonction qui affiche un carré, et une fonction qui affiche le menu et renvoie le choix obtenu. Modifiez la fonction principale pour qu'elle utilise ces trois fonctions.

  2. Miroir. Reprenez le programme du quatrième exercice du sujet sur les tableaux. Définissez une fonction qui affiche le tableau, une fonction qui le remplit de valeurs aléatoires, et une fonction qui inverse le tableau. Modifiez la fonction principale pour qu'elle utilise ces trois fonctions.

  3. Zéro. Voici une fonction qui met une variable à zéro, dans un programme qui illustre son usage :

    void zero(double a) {
      a = 0.0;
    }
     
    int main(void) {
      double x=37.5;
      printf("avant : %f\n", x);
      zero(x);
      printf("après : %f\n", x);
      return EXIT_SUCCESS;
    }

    Testez cet exemple. Est-ce que la fonction fait son travail ? Pouvez-vous expliquer pourquoi ? Modifiez la définition de la fonction et son appel pour que cela marche comme prévu.

  4. Échange. Écrivez une fonction qui intervertit les valeurs de deux variables. Servez-vous en pour simplifier la fonction d'inversion du deuxième exercice.

  5. Produit. Écrivez un programme qui prend un nombre quelconque d'entiers sur la ligne de commande et affiche leur produit. On souhaite que les arguments qui ne sont pas des entiers soient ignorés. Lisez la page manuelle de la fonction strtol et servez-vous en pour repérer les arguments inutilisables.

retour à la page d'accueil

retour au sommet