Informasjon

Frist Ingen frist
Innleveringsgrense Ingen begrensning

Merkelapper

Logg inn

[5.1] Rappel des concepts [Obligatoire]

L'abstraction procédurale et les fonctions

L'abstraction procédurale est un principe de programmation qui consiste à structurer un programme en un ensemble de fonctions (ou procédures) qui sont des séquences d'instructions qui permettent de produire des résultats ou des effets sans exposer leurs implémentations. L'objectif de ce principe est que chaque fonction accomplisse une tâche spécifique et généralement simple pour qu'en les combinant, elles résolvent un problème global. Cela permet de rendre le code plus lisible et de réutiliser des fragments de code dans différents contextes au lieu de juste dupliquer ces fragments.

Pour créer une fonction en C, on va faire ce qu'on appelle une définition de fonction. Elle est composée de plusieurs éléments :

  • une précondition qui est une condition devant être vraie lors de l'appel de la fonction (similaire à la précondition des boucles).
  • une postcondition qui est une condition devant être vraie lors de la sortie de la fonction (similaire à la postcondition des boucles).
  • un type de retour qui indique le type de valeur que la fonction renvoie (ou void si elle ne renvoie rien).
  • un nom (ou identifiant) qui identifie la fonction.
  • une liste de paramètres (ou arguments formels) entre parenthèses qui spécifie les entrées (et parfois les sorties) de la fonction (ou void si elle n'a pas de paramètres).
  • un corps entre accolades qui contient les instructions que la fonction exécute.

Certains de ces éléments peuvent être regroupés pour former des concepts plus larges :

  • les spécifications de la fonction qui comprennent la précondition et la postcondition.
  • l'en-tête (ou signature) de la fonction qui comprend le type de retour, le nom et la liste des paramètres.

Exemple

Un exemple de fonction que vous avez déjà vue est la fonction main qui est le point d'entrée de tout programme C, par exemple :

int main(void) {
    int a = 5;
    return 0;
}

Comme vous pouvez le voir, la fonction main a un type de retour int, pas de paramètres (indiqué par void) et son corps contient deux instructions : la déclaration et l'initialisation d'une variable entière a avec la valeur 5 et une instruction return pour renvoyer la valeur 0 qui indique que le programme s'est terminé avec succès à l'OS. Dans le cas de la fonction main, on omet souvent de mentionner explicitement la précondition et la postcondition car elles sont implicites mais c'est une bonne pratique de les inclure pour les autres fonctions.

Un autre exemple simple est la fonction max qui prend deux entiers en paramètres et renvoie le plus grand des deux :

// PRE: /
// POST: renvoie le plus grand des deux entiers a et b.
int max(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

Ici, la fonction max a un type de retour int, deux paramètres entiers a et b, et un corps qui utilise une sélection pour déterminer lequel des deux entiers est le plus grand et le renvoyer. La précondition est vide (indiquée par /) car les types des paramètres garantissent déjà que les entrées soient des entiers donc il n'y a pas de conditions supplémentaires à vérifier, et la postcondition indique plus précisément ce que la fonction fait.

Les appels de fonctions

Pour utiliser une fonction, on va faire ce qu'on appelle un appel de fonction. Cela consiste à écrire le nom de la fonction suivi de parenthèses contenant les arguments (ou arguments effectifs) de la fonction. Voici un exemple d'appel de la fonction max définie précédemment :

Dans cet exemple, vous pouvez voir l'intérêt des fonctions pour réutiliser des fragments de code car, plutôt que de dupliquer le if qui compare les deux entiers, on peut simplement appeler la fonction max chaque fois qu'on a besoin de trouver le maximum entre deux entiers.

Vous pouvez également voir un comportement important des fonctions : elles créent un nouvel environnement d'exécution. Quand l'on passe des arguments à une fonction, ces arguments sont copiés dans les paramètres de la fonction. Cela signifie que les modifications apportées aux paramètres à l'intérieur de la fonction n'affectent pas les arguments originaux dans l'environnement d'appel (même si les paramètres et les arguments ont le même nom). C'est ce qu'on appelle le passage par valeur qui est le mode de passage des arguments en C. Cependant, en C, on peut faire passer par valeur une référence à une variable grâce à l'utilisation de pointeurs. Dans ce cas, on appelle plutôt cela le passage par référence et cela permet à la fonction de modifier la variable originale. Voici un exemple pour illustrer cela :

Grâce à cet exemple, vous avez normalement mieux compris ce qu'il se passe en réalité quand vous appelez les fonctions printf et scanf que vous avez utilisées dans les exercices précédents. Si vous vous souvenez bien, on avait dit qu'on allait voir plus tard comment fonctionnaient ces fonctions et c'est ce que nous venons de faire.

Cet exemple permet aussi de rappeler le concept d'argument de sortie qui permet de retourner une valeur via une référence passée en argument. Voici un petit récapitulatif des différents types d'arguments :

  • Argument d'entrée : un argument qui est utilisé pour fournir des valeurs à la fonction.
  • Argument de sortie : un argument qui est utilisé pour renvoyer des valeurs de la fonction vers l'appelant via une référence.
  • Argument d'entrée-sortie : un argument qui est utilisé à la fois pour fournir des valeurs à la fonction et pour renvoyer des valeurs de la fonction vers l'appelant via une référence.

Les fichiers d'en-tête

Comme dit juste avant, vous avez utilisé les fonctions printf et scanf mais comment est-ce que cela est possible alors vous ne les avez pas définies ? En réalité, la majorité des fonctions qu'on utilise quand on programme ont déjà été écrites par d'autres personnes qui avaient le même besoin que nous. Ces fonctions sont définies dans des modules regroupés dans des bibliothèques (ou librairies) comme le module stdio de la bibliothèque standard qui contient les fonctions printf et scanf. Pour mettre à disposition ces fonctions, on utilise des fichiers d'en-tête qui contiennent les déclarations des fonctions présentes dans ces modules. Une déclaration de fonction est similaire à une définition de fonction mais elle ne contient pas le corps de la fonction car la fonction a déjà été compilée et donc on a juste besoin d'avoir suffisamment d'informations pour savoir comment l'appeler correctement. Par exemple, la déclaration de la fonction max serait juste :

// PRE: n >= 0
// POST: renvoie le plus grand des deux entiers a et b.
int max(int a, int b);

Pour que le compilateur comprenne que l'on veut utiliser un module, on utilise la macro #include suivie du nom de son fichier d'en-tête entre "" pour les modules locaux et entre <> pour les modules globaux. Par exemple, pour utiliser les fonctions printf et scanf du module stdio, on va inclure le fichier d'en-tête stdio.h comme vous l'avez déjà fait dans les exercices précédents :

#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}

Par contre, si on avait créé un module local pour la fonction max et qu'on avait mis sa déclaration dans un fichier d'en-tête max.h, on l'inclurait comme ceci :

#include "max.h"

int main(void) {
    int m = max(3, 5);
    return 0;
}

Nous verrons dans la section suivante comment créer nos propres fichiers d'en-tête, modules et bibliothèques mais pour l'instant, nous allons nous concentrer sur les appels de fonctions déjà définies.


Spørsmål 1: Les différents concepts liés à une fonction
// PRE: n >= 0
// POST: renvoie le carré de x.
int power2(int x) {
    return x * x;
}

Étant donné le programme ci-dessus, associez chaque élément présent avec le nom qui lui correspond :

Spørsmål 2: Spécification (partie 1)

Je suis une proposition à propos des valeurs des paramètres d'une fonction (et des variables globales utilisées par celle-ci) qui devrait être vraie lors d’un appel de cette fonction.

(Par exemple, la fonction factorielle a un argument formel et ne garantit de renvoyer le résultat attendu que si, au moment de l'appel à cette fonction, son argument effectif est positif).

Je suis une ...

Spørsmål 3: Spécification (partie 2)

Je suis une proposition à propos de la valeur de retour et des valeurs des paramètres d'une fonction (et des variables globales utilisées par celle-ci) qui devrait être vraie lors de la sortie de la fonction.

Je suis une...

Spørsmål 4: Arguments effectifs
// PRE: n >= 0
// POST: renvoie n!
int factorielle(int n) {
    int resultat = 1;
    // INV: resultat = PROD i=n jusque n_0 de n && 1 <= n <= n_0
    while (n > 1) {
        resultat = resultat * n;
        n--;
    }
    return resultat;
}

int main(void) {
    int nombre = read_int();
    int fact = factorielle(nombre);
    printf("%i", fact);
    return 0;
}

Dans le programme ci-dessus, quels sont les arguments effectifs des différents appels de fonctions (dans leur ordre d'apparition, séparés par un espace à chaque fois) :

Spørsmål 5: Fonction (partie 1)

Je donne le type que la fonction renvoie, le nom de la fonction, et les noms et types des arguments formels de la fonction.

Je suis l'...

Spørsmål 6: Fonction (partie 2)

Je suis un bloc d'instruction contenant les calculs que la fonction doit effectuer et je finis généralement par un return qui détermine la valeur renvoyée par la fonction.

Je suis le ... de la fonction.

Spørsmål 7: Appels de fonctions
// PRE: n >= 0
// POST: renvoie n!
int factorielle(int n){
    int resultat = 1;
    // INV: resultat = PROD i=n jusque n_0 de n && 1 <= n <= n_0
    while (n > 1) {
        resultat = resultat * n;
        n--;
    }
    return resultat;
}

int main(void) {
    int nombre;
    scanf("%i", &nombre);
    int fact = factorielle(nombre);
    printf("La factorielle de %i est %i\n", nombre, fact);
    return 0;
}

Dans le programme ci-dessus, quels sont les noms des fonctions qui sont appelées (dans leur ordre d'apparition, séparés par un espace à chaque fois) :

Spørsmål 8: Variables (partie 1)

Une variable a un identifiant, un type et une valeur. Mais l'ordinateur a besoin aussi de savoir où cette variable se trouve dans la mémoire. Ainsi, de même que nous avons tous une adresse qui permet de savoir où on est, chaque variable a une adresse qui indique où elle se trouve dans l'ordinateur. Cette adresse, on l'appelle la ... d'une variable.

Spørsmål 9: Variables (partie 2)

À partir d'une variable, il est possible de récupérer son adresse (ou sa référence) au moyen d'un opérateur.

Quel est cet opérateur ?

Spørsmål 10: Variables (partie 3)

À partir d'une référence/adresse d'une variable, il est possible de récupérer la valeur de cette variable grâce à l'opérateur de déréférencement.

Quel est cet opérateur ?

Spørsmål 11: Générateur de mots de passe (partie 1)

Étant donné le fichier d'en-tête password_utils.h suivant :

// PRE: /
// POST: renvoie true si nb est un nombre premier, false sinon
bool est_premier(int nb);

// PRE: /
// POST: renvoie la somme des chiffres de nb
int somme_chiffres(int nb);

// PRE: 0 <= nb <= 31
// POST: renvoie la lettre de l'alphabet qui correspond au nombre entré (0->a, 1->b, ...)
char vers_lettre(int nb);

Et le programme suivant :

#include "password_utils.h"

#include <stdio.h>

int main(void) {
    int min, max;
    scanf("%d %d", &min, &max);

    int somme = 0;
    while (min <= max) {
        if (est_premier(min)) {
            sc = somme_chiffres(min);
            somme += sc;
            printf("%c", vers_lettre(sc % 26));
        }
        min++;
    }

    printf("%d\n", somme);

    return 0;
}

Combien de valeurs l'utilisateur doit-il entrer ?

Spørsmål 12: Générateur de mots de passe (partie 2)

Étant donné le même programme que dans la question précédente, qu'est-ce qui garantit que, lors de l'appel à la fonction vers_lettres, la précondition de cette fonction est respectée ?

Spørsmål 13: Générateur de mots de passe (partie 3)

Étant donné le même programme que dans la question précédente, qu'est-ce qui s'affiche si l'utilisateur entre les valeurs 2 et 14 ?