tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
ISIMA
  • twitter
  • linkedin
  • viadeo

[C] Hall of Fame - saison 1

Date de première publication : 2015/10/06

On va s'intéresser à la programmation d'un Hall of Fame : on mémorisera les meilleures scores sur des jeux avec l'identiifant avec lequel on a joué...

Les informations seront stockées dans des structures particulières quue nous allons utiliser de deux manières différentes :

La base du TP se trouve là :

git clone https://gitlab.com/kiux/C4.git

Une saisie sécurisée

Jusqu'à maintenant, on vous a laissé utiliser la fonction scanf() pour faire de la saisie. Cette fonction est dangereuse (il n'y a aucun test de dépassement de mémoire tampon), il faut donc plutôt privilégier la fonction fgets(). Celle-ci a deux avantages :

Elle a, en revanche un inconvénient, la chaîne intègre le plus souvent le caractère de fin de saisie '\n', ce qui ne nous intéresse pas forcément même s'il n'est plus nécessaire de l'enlever de la mémoire tampon associée au fichier.

Voici un usage tout simple :

char saisie[255];
fgets(saisie, 255, stdin);

Si la chaîne de caractères saisie représente un nombre, il est possible de l'obtenir par conversion avec des fonctions comme atoi() "ascii to integer", atof(), strtol() ou bien encore sscanf(). Les fonctions sscanf() ne sont absolument pas déconseillées

On vous conseille de vous faire une petite fonction équivalente du fgets() mais qui enlève le '\n' de fin de saisie s'il est présent.

Le fichier de test associé au TP montre un petit exemple d'utilisation de fgets()

TEST(fgets) {
   char exemple [] = "scanf, c'est pas bien\n";
   LOG(exemple);
   char chaine1[25];
   char chaine2[10];

   FILE * file = fmemopen(exemple, sizeof(exemple)+1, "r");
   // REQUIRE ( NULL != file);

   fgets(chaine1, 25, file);
   LOG(chaine1);
   fclose(file);
   REQUIRE( strlen(exemple) == strlen(chaine1) );

   file = fmemopen(exemple, sizeof(exemple)+1, "r");
   // REQUIRE ( NULL != file);

   fgets(chaine2, 10, file);
   LOG(chaine2);
   
   REQUIRE( strlen(exemple) > strlen(chaine2) );

   fgets(chaine2, 10, file);
   LOG(chaine2);
   fclose(file);
}

LOG() est une macro de type fonction qui affiche le message donné en paramètre sur la sortie d'erreur standard

#define LOG(A) do {             \
    fprintf(stderr, "%s\n", A); \
} while(0) 

Structures et tableaux

Première rencontre avec un structure

La suite du code se met dans une fonction :

Ce qu'il faut faire pour le TP...

TEST(Sizeof) {
  int taille1 = sizeof(struct donnee);
  int taille2 = sizeof(int)+100*sizeof(char); // :-)

  CHECK (taille1 == taille2);
}
TEST(AffichageB) {
   // declaration et initialisation d'une variable de type struc
   struct donnee essai;
   
   strcpy(essai.nom, "pokemon GO");
   strcpy(essai.alias, "loic");
   essai.score = 498;

   afficherDonnee(stdout, essai); 

   // creation du flux de texte => buffer
   char buffer[1024];
   FILE * file = fmemopen(buffer, 1024, "w");
   REQUIRE ( NULL != file);

   afficherDonnee(file, essai);
   
   fclose(file);

   CHECK( 0==strcmp(buffer, "pokemon GO : loic avec 498\n") );
}

Il y a deux tests différents (B et C) pour l'affichage. Cela permet de vérifier que les types struct donnee et donnee_t sont bien utilisables dans le programme.

Si le test est correct, vous pouvez sauvegarder ("commit") votre travail sur le dépôt

TEST(Saisie) {
   struct donnee essai;
   char buffer[1024];
   strcpy(buffer, "rien\ndutout\n10");
   FILE * file = fmemopen(buffer, 1024, "r");
   // REQUIRE ( NULL != file);

   saisirDonnee(file, &essai);
   fclose(file);

   afficherDonnee(stdout, essai);

   CHECK(  0 == strcmp(essai.nom, "rien") );
   CHECK(  0 == strcmp(essai.alias, "dutout") );
   CHECK( 10 == essai.score );   
}

Là encore, la fonction saisirDonnee()n'ouvre pas le flux (fichier ou autre), elle ne fait que l'utiliser.

Le test unitaire proposé n'impose rien sur le type de retour de la fonction saisirDonnee(). Il pourrait être pertinent que la fonction renvoie 0 si un problème a eu lieu pendant la saisie (fgets()revoie NULL en cas de problème) et une valeur non nulle sinon.

Pour cette question, il est recommandé d'avoir écrit la petite fonction qui enlève le saut de ligne '\n'en dernier caractère !

2048
loic
64236
Minecraft
kiux
12304883

Si le fichier contient plus de TAILLE_MAX éléments, les éléments supplémentaires devront être écartés.

TEST(lectureFichier) {
   donnee_t tableau[TAILLE_MAX];
   int taille = 0;
   
   // test d'un fichier non existant
   taille = tableauFromFilename("inconnu.txt", tableau);      
   CHECK( 0 == taille );

   // test du fichier exemple
   taille = tableauFromFilename("jeu1.txt", tableau);

   REQUIRE( 2 == taille );
   CHECK  ( 0 == strcmp(tableau[0].nom, "2048"));
   CHECK  ( 0 == strcmp(tableau[0].alias, "loic"));
   CHECK  ( 64236 == tableau[0].score );
   CHECK  ( 0 == strcmp(tableau[1].nom, "Minecraft"));
   CHECK  ( 0 == strcmp(tableau[1].alias, "kiux"));
   CHECK  ( 12304883 == tableau[1].score );
}

Aller plus loin

La prochaine étape est de passer d'un tableau de structures à une liste chaînée.