Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Organisation en plusieurs fichiers sources

Ah non

Bien sûr, un looooooooog fichier, ce n’est pas maintenable.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// Structure pour associer un Katakana à sa prononciation (Romaji)
typedef struct {
    char *katakana;
    char *romaji;
} Kana;

// Base de données des Katakanas de base
Kana baseKatakana[] = {
    {"ア", "a"}, {"イ", "i"}, {"ウ", "u"}, {"エ", "e"}, {"オ", "o"},
    {"カ", "ka"}, {"キ", "ki"}, {"ク", "ku"}, {"ケ", "ke"}, {"コ", "ko"},
    {"サ", "sa"}, {"シ", "shi"}, {"ス", "su"}, {"セ", "se"}, {"ソ", "so"},
    {"タ", "ta"}, {"チ", "chi"}, {"ツ", "tsu"}, {"テ", "te"}, {"ト", "to"},
    {"ナ", "na"}, {"ニ", "ni"}, {"ヌ", "nu"}, {"ネ", "ne"}, {"ノ", "no"},
    {"ハ", "ha"}, {"ヒ", "hi"}, {"フ", "fu"}, {"ヘ", "he"}, {"ホ", "ho"},
    {"マ", "ma"}, {"ミ", "mi"}, {"ム", "mu"}, {"メ", "me"}, {"モ", "mo"},
    {"ヤ", "ya"}, {"ユ", "yu"}, {"ヨ", "yo"},
    {"ラ", "ra"}, {"リ", "ri"}, {"ル", "ru"}, {"レ", "re"}, {"ロ", "ro"},
    {"ワ", "wa"}, {"ヲ", "wo"}, {"ン", "n"}
};

int totalKana = sizeof(baseKatakana) / sizeof(baseKatakana[0]);

int main() {
    int score = 0;
    int nombreQuestions = 10; // Nombre de questions par partie

    // Initialisation de l'aléatoire
    srand(time(NULL));

    printf("=== QCM : APPRENDRE LES KATAKANAS ===\n");
    printf("Trouvez la bonne transcription (Romaji) pour chaque symbole.\n\n");

    for (int q = 1; q <= nombreQuestions; q++) {
        // 1. Choisir un Katakana aléatoire pour la question
        int indexCorrect = rand() % totalKana;
        Kana cible = baseKatakana[indexCorrect];

        // 2. Choisir à quelle position (1 à 4) sera la bonne réponse
        int bonneReponsePos = (rand() % 4) + 1;
        int choix[4];

        // Remplir les 4 choix (1 correct + 3 faux uniques)
        choix[bonneReponsePos - 1] = indexCorrect;
        for (int i = 0; i < 4; i++) {
            if (i == bonneReponsePos - 1) continue;
            
            int fauxIndex;
            int dejaPresent;
            do {
                dejaPresent = 0;
                fauxIndex = rand() % totalKana;
                // Vérifier qu'on ne met pas deux fois le même choix
                if (fauxIndex == indexCorrect) {
                    dejaPresent = 1;
                }
                for (int j = 0; j < i; j++) {
                    if (choix[j] == fauxIndex) {
                        dejaPresent = 1;
                    }
                }
            } while (dejaPresent);

            choix[i] = fauxIndex;
        }

        // 3. Afficher la question
        printf("--- Question %d / %d ---\n", q, nombreQuestions);
        printf("Quel est ce Katakana : [ %s ] ?\n", cible.katakana);
        for (int i = 0; i < 4; i++) {
            printf("%d) %s\n", i + 1, baseKatakana[choix[i]].romaji);
        }

        // 4. Lire la réponse de l'utilisateur
        int reponse;
        printf("Votre choix (1-4) : ");
        if (scanf("%d", &reponse) != 1) {
            printf("Entrée invalide. Passage à la question suivante.\n");
            while (getchar() != '\n'); // Vider le buffer
            continue;
        }

        // 5. Vérifier la réponse
        if (reponse == bonneReponsePos) {
            printf("Bravo ! C'etait bien '%s'.\n\n", cible.romaji);
            score++;
        } else {
            printf("Dommage... La bonne réponse était %d) %s\n\n", bonneReponsePos, cible.romaji);
        }
    }

    // 6. Score final
    printf("=== FIN DU QUIZ ===\n");
    printf("Votre score final : %d / %d\n", score, nombreQuestions);
    if (score == nombreQuestions) {
        printf("Parfait ! Vous maîtrisez vos Katakanas !\n");
    } else if (score >= nombreQuestions / 2) {
        printf("Pas mal ! Encore un petit effort.\n");
    } else {
        printf("Il faut encore réviser un peu ! \n");
    }

    return 0;
}

Fichiers source et fichiers header

On découpe le projet en plusieurs fichiers.

Fichiers source

Un fichier source .c contient les définitions des fonctions.

Un fichier fonction_mathematiques.c contient par exemple :

#include "fonctions_mathematiques.h"

int addition(const int a, const int b) {
    return a + b;
}

int multiplication(const int a, const int b) {
    return a * b;
}

int division(const int a, const int b) {
    return a / b;
}

int sousroutine(int x) {
    ...
}

...

Fichiers header

Un fichier header .h contient des déclarations de fonctions, ainsi que les spécifications.

Voici un exemple de fichier header (vous pouvez écrire les spécifications avec l’extension Doxygen Documentation) :

/**
 * @brief perform an addition in O(1)
 * 
 * @param a 
 * @param b 
 * @return the sum of a and b
 */
int sum(const int a, const int b);

/**
 * @brief perform a multiplication in O(1)
 * 
 * @param a 
 * @param b 
 * @return the product of a and b
 */
int product(const int a, const int b);

/**
 * @brief perform an Euclidean division
 * 
 * @param a 
 * @param b non zero
 * @return the Euclidean division of a and b. If b is non zero, the behavior is not defined.
 */
int division(const int a, const int b);
...

Attention aux inclusions circulaires

Si main.c inclut fonctions_mathematiques.h et bloup.h qui lui aussi inclut fonctions_mathematiques.h, on inclut fonctions_mathematiques.h deux fois ! Ca ne va pas compiler !

Solution traditionnelle : un #define artificiel

#ifndef FONCTIONS_MATHEMATIQUES_H
#define FONCTIONS_MATHEMATIQUES_H

/**
 * @brief perform an addition in O(1)
 * 
 * @param a 
 * @param b 
 * @return the sum of a and b
 */
int sum(const int a, const int b);

/**
 * @brief perform a multiplication in O(1)
 * 
 * @param a 
 * @param b 
 * @return the product of a and b
 */
int product(const int a, const int b);

/**
 * @brief perform an Euclidean division
 * 
 * @param a 
 * @param b non zero
 * @return the Euclidean division of a and b. If b is non zero, the behavior is not defined.
 */
int division(const int a, const int b);

#endif

Solution moderne : #pragma once

#pragma once

/**
 * @brief perform an addition in O(1)
 * 
 * @param a 
 * @param b 
 * @return the sum of a and b
 */
int sum(const int a, const int b);

/**
 * @brief perform a multiplication in O(1)
 * 
 * @param a 
 * @param b 
 * @return the product of a and b
 */
int product(const int a, const int b);

/**
 * @brief perform an Euclidean division
 * 
 * @param a 
 * @param b non zero
 * @return the Euclidean division of a and b. If b is non zero, the behavior is not defined.
 */
int division(const int a, const int b);

Exercice

Découper votre projet :

  • Fichier dynarray.c qui contient les définitions pour utiliser un dynarray
  • Fichier dynarray.h qui ne contient que les déclarations et spécifications des fonctions
  • Fichier main.c qui utilise dynarray et qui inclut dynarray.h
  • Compiler avec gcc dynarray.c main.c -o main -Wall

  • Cloner le dépôt qui contient le jeu de tir en C dans le terminal https://git.aliens-lyon.fr/enslyon-l3-prog/shmup_in_c
  • Le lancer.
  • Découper le projet en plusieurs fichiers pour améliorer la lisibilité et l’extension futur.
  • Ajouter des explosions quand un tir touche un ennemi
  • Ajouter le déplacement des étoiles.

Dossiers

Dans des très grands projets, afin de pouvoir distribuer plus facilement les .h importants, on met tous les fichiers source .c et .h intermédiaires dans un dossier src et les fichiers header importants dans un dossier include. On organise le code par sous-dossiers.