Date de première publication : 2015/10/06
On va s'intéresser à la programmation d'un Hall of Fame : on mémorisera les meilleurs scores sur des jeux avec l'identifiant 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 :
- séance 1 : un tableau de structures (statique ou dynamique)
- séance 2 : une liste chaînée
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 :
- permettre de saisir des espaces
- mais surtout de contrôler la taille de la chaîne de caractères.
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.
char exemple [] = "scanf, c'est pas bien\n";
char chaine1[25];
char chaine2[10];
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
Créer un fichier main.c
et lui mettre une fonction int main()
- Créer une structure
struct donnee
, renomméedonnee_t
(partypedef
) qui contient les éléments suivants : - un score entier
- un nom de jeu, une chaîne de 100 caractères
- un alias de joueur, une chaîne de 40 caractères
Essayer les choses suivantes :
- Créer une variable locale
essai
de typestruct donnee
. - Initialiser chacun des champs avec les valeurs que vous voulez
- Afficher les differents champs de la variable
essai
- Recommencer avec un type
donnee_t
- Créer le pointeur
p
et afficher les champs deessai
struct donnee * p = &essai;
les notations (*p).score
et p->score
sont équivalentes
On va maintenant vérifier l'espace mémoire occupé par une structure : comparer
sizeof(int)
, char[40]
et sizeof(donnee_t)
- Écrire la fonction
afficherDonnee()
qui prend en paramètre une structure et l'affiche sur le flux passé en paramètre.
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
- Écrire une fonction
saisirDonnee()
qui permet de saisir les données de la structure en paramètre (sic !) à partir du flux donné. Regardez bien le test pour voir le protype et l'utilisation de la fonction tels que je les ai imaginés.
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 !
- Définir une constante symbolique
TAILLE_MAX
d'au moins 50 dans le fichier .h adéquat - Écrire la fonction
tableauFromFilename()
qui permet de lire les données à partir d'un fichier texte dont voici un exemple avec deux jeux :
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
- Si vous avez utilisé un tableau statique, vous pouvez désormais utiliser un tableau dynamique
- Les chaînes de caractères de la structure de données pourraient également être allouées dynamiquement
- Écrire une fonction qui permet de sauvegarder le tableau dans un fichier texte
- Écrire un programme qui réutilise ces fonctions et qui permet à l'utilisateur de gérer une liste de scores
La prochaine étape est de passer d'un tableau de structures à une liste chaînée.