Chapitre 5 • structures defines enums
| Champ | Valeur |
|---|---|
| Auteur·e | Élise |
| Édition | 2023-11-23 |
| Durée | 3 sessions |
| Taille des équipes | 1 personne |
| Rendu | via git, dépôt $YEAR_ep_c5, droits en lecture à delivery_collector |
Barème
| Critères | Points |
|---|---|
| add | 2 |
| operandor e1 structure | 2 |
| operandor e2 allocations | 3 |
| operandor e3 opérations | 2 |
| énumérations et opérations simples | 2 |
| tableau d’int e1 déclaration et allocation | 3 |
| tableau d’int e2 push | 2 |
| tableau d’int e3 somme | 2 |
| tableau d’int e4 moyenne | 2 |
Règlement
La réalisation de cet exercice est assujettie aux règles en vigueur dans l’école en matière de triche et de normalisation. Tricher vous expose à de graves sanctions.
Vous devez respecter les règles de normalisation suivantes : https://git.ecole-89.com/eriizu/coding_style/src/branch/main/norm.md
Le non-respect de la norme vous fera perdre une partie ou la totalité des points qui auraient pu être acquis, sur l’ensemble du rendu.
Instructions de rendu
Respectez à la lettre les noms de fichiers et leur emplacement dans le dépôt de rendu. S’il vous est demandé de rendre un fichier nommé hello.c sans qu’un nom de dossier soit précisé : rendez un fichier hello.c à la racine de votre dépôt.
La présence d’une étoile * signifie que le nom du fichier peut être n’importe lequel dès lors qu’il porte l’extension ou l’affixe (préfixe/suffixe) demandée. Elle signifie aussi qu’il est possible de rendre plusieurs fichiers.
La présence d’une double étoile ** signifie que les fichiers peuvent être placés n’importe où dans un dossier, y compris dans des sous-dossiers.
Pensez à faire des commits et des push fréquemment. Autrement nous ne pouvons pas vous aider à retrouver vos fichiers perdus.
Fonctions autorisées et interdites
Par défaut, toute fonction système (comme write) ou fonction des bibliothèques (comme printf ou puts) sont interdites.
À chaque exercice vous sera donné une liste de fonctions autorisées, le cas échéant.
main de test
Chaque fois que vous devez rendre une fonction, vous devez aussi rendre un fichier *.main.c. Il s’agit du fichier qui doit contenir votre main de test.
Vous devez écrire des tests pour voir si votre fonction à le bon comportement.
Correction du code source en C
Tout le code source sera compilé avec les drapeaux suivants :
-Wall -Wextra -Werror
Testez votre code avec ces drapeaux pour que le compilateur vous informe de tout ce qui peut poser problème avec votre programme avant de le rendre.
Pour tester qu’un fichier sans main compile, vous devez le compiler contre un second fichier qui lui contient un main.
gcc -Wall -Wextra -Werror example.c example.main.c
Exercice : paramètres d’une opération simple
Fichier·s à rendre : add.h, add.c, add.main.c
Fonction·s autorisée·s : aucune
Étape 1 : structure
Écrivez une structure add dans le fichier add.h. Elle doit avoir 3 champs, tous capable de stocker un nombre entier. Les 2 premier doivent correspondre aux opérandes de l’opération (les deux nombres à additionner), le dernier au résultat de l’opération.
Qu’est-ce qu’un .h ?
Les fichiers .h sont des fichiers que l’on peut charger depuis un .c ou d’autres .h. Pour éviter de charger plusieurs un même fichier (ce qui peut avoir un effet catastrophique à la compilation), on doit respecter une certaine structure :
#ifndef ADD_H_
#define ADD_H_
// [vos structures, constantes symboliques et prototypes à la place de ce commentaire]
struct add {
// [...]
};
#endif
Étape 2: fonction
Écrivez une fonction stu_add dans add.c qui prend en paramètre un pointeur sur une struct add. La fonction ne doit pas avoir de valeur de retour. Elle doit mettre le résultat de l’opération dans le champ prévu à cet effet dans la structure pointée.
Écrivez le prototype de la fonction add dans le fichier add.h.
Étape 3 : appel
Testez votre fonction dans add.main.c. Voici un exemple de test. Les noms de vos champs ne sont pas nécessairement les même.
#include <stdio.h>
#include "add.h"
static void print_add(struct add *to_print)
{
printf("%d + %d == %d\n", to_print->rhs, to_print->lhs, to_print->result);
}
int main(void)
{
struct add add_1;
add_1.rhs = 12;
add_1.lhs = 32;
stu_add(&add_1);
print_add(&add_1);
}
Exercice : opérandor!
Fichier·s à rendre : operandor.h, operandor.c, operandor.main.c
Fonction·s autorisée·s : cas par cas
Étape 1 : structure
Écrivez une structure operandor dans le fichier operandor.h. Elle doit prévoir des champs (propriétés) pour :
- deux nombres entiers (nos opérandes) ;
- un caractère (notre opérateur).
Étape 2 : allocation
Il y a plusieurs façon de construire une structure. On peut l’allouer sur le pile en la déclarant comme une simple variable, puis initialiser ses champs directement où via une fonction qui prend un pointeur sur la structure :
int main(void)
{
struct operandor op_1;
operandor_init(&op_1, 89, 112, '+');
// [...]
return (0);
}
On peut aussi décider de déclarer un pointeur sur la structure, et l’allouer sur le tas avec malloc. C’est d’autant plus pratique lorsqu’on veut pouvoir renvoyer la structure depuis une fonction de création.
int main(void)
{
struct operandor *op_1;
op_1 = malloc(sizeof(struct operandor));
operandor_init(op_1, 89, 112, '+');
// [...]
free(op_1);
return (0);
}
Dans operandor.c, écrivez les fonctions :
operandor_create:- prend en paramètre deux int, et un caractère,
- si le caractère passé n’est pas
+,-,/,*,%la fonction doit renvoyerNULL, - renvoie pointeur sur une instance de la structure nouvellement allouée et remplie,
- le pointeur renvoyé doit pouvoir être passé à
freepar la fonction appelante, - fonction autorisée :
malloc;
operandor_init:- prend en paramètre un pointeur vers la structure, deux int, et un caractère,
- si le caractère passé n’est pas
+,-,/,*,%la fonction doit renvoyer 0, - elle remplit l’instance de la structure en paramètre,
- renvoie 1 si elle a réussi,
- fonctions autorisées : aucunes.
Dans operandor.h, écrivez leur prototypes.
Testez les deux fonctions dans votre operandor.main.c (fichier dans lequel vous pouvez include "operandor.h"), des points sont accordés aux tests.
Étape 3 : opérations
Le stockage c’est fait ! Maintenant exécutons l’opération stockée dans notre structure. Écrivez la fonction operandor_compute. Elle doit prendre en paramètre un pointeur sur notre structure et elle doit renvoyer un nombre entier.
La valeur que doit renvoyer la fonction est le résultat de l’opération.
L’opération à faire dépend du caractère stocké dans la structure dont vous recevez l’adresse.
Testez votre fonction dans operandor.main.c. Des points sont accordés aux tests.
Exercice : énumération et opérations simples
Fichier·s à rendre : enum_op.h, enum_op.c, enum_op.main.c
Fonction·s autorisée·s : aucune
Vous devez écrire la fonction enum_op qui prend en paramètre une énumération de votre création et deux nombres entiers. La fonction doit faire l’opération qui lui ai demandé par le premier paramètre.
Voici un main d’exemple auquel votre fonction et votre énumération doivent se conformer :
#include enum_op.h
int main(void)
{
int res;
res = enum_op(OP_ADD, 113, 43);
printf("%d\n", res);
res = enum_op(OP_MUL, 4, 4);
printf("%d\n", res);
res = enum_op(OP_SUB, 113, 43);
printf("%d\n", res);
res = enum_op(OP_DIV, 100, 50);
printf("%d\n", res);
res = enum_op(OP_MOD, 113, 2);
printf("%d\n", res);
}
Dont la sortie attendue est :
156
16
70
2
1
Exercice : tableau d’int
Fichier·s à rendre : intarr.h, intarr.c, intarr.main.c
Fonction·s autorisée·s : cas par cas
Lorsqu’on a vu les bases sur les tableaux, on a vu qu’il était nécessaire de savoir où est-ce que le tableau s’arrête. À défaut c’est le segmentation fault que l’on risque.
Pour les chaînes de caractères, la fin du tableau c’est le \0. Pour les arguments du programme, dans le main, la fin du tableau c’est argc - 1. Quand on veut faire notre propre tableau d’int, 0 ou -1 peuvent être des valeurs que l’on veut stocker, auquel cas on doit absolument avoir un autre mécanisme pour connaitre la fin du tableau.
Le mécanisme le plus fréquent, c’est de simplement stocker la taille du tableau quelque part. Sauf que se trimbaler un tableau et sa taille un peu partout, c’est pas très pratique. Ça signifie que deux paramètres au moins seraient nécessaires à chacune des fonctions qui travaille sur le tableau. C’est fort peu pratique.
Heureusement, il existe une solution plus pratique qui s’approche d’avantage de ce que l’on trouve dans les langages de programmation à haut niveau d’abstraction. C’est d’utiliser une structure pour garder l’adresse de notre tableau et sa taille. On peut même faire mieux et différencier la capacité allouée de la taille utilisée.
Étape 1 : déclaration et allocation
Fonction·s autorisée·s : malloc, free
Créez une structure intarr avec trois champs :
- un pointeur sur
int - un entier non signé qui représente la taille utilisée du tableau
- un entier non signé qui représente la capacité allouée du tableau
Implémentez la fonction intarr_create. Elle prend en paramètre la capacité initiale avec laquelle elle doit allouer le tableau.
Étapes :
mallocunintarr;mallocle tableau d’intdans l’instance d’intarrtout juste allouée avec pour quantité celle passée en paramètre ;- mettre la taille du tableau à 0 ;
- mettre la capacité du tableau à celle passée en paramètre ;
- renvoyer l’instance d’
intarr(l’adresse donnée par le premiermalloc).
Implémentez la fonction intarr_delete. Elle prend en paramètre un pointeur sur structure intarr. Son métier est de free toute la mémoire allouée dans intarr_create.
main d’exemple :
#include "intarr.h"
int main(void)
{
struct intarr *my_array;
my_array = intarr_create(10);
intarr_delete(my_array);
return (0);
}
Compilez puis exécutez avec valgrind. Si vous voyez le message all heap blocks were freed -- no leaks are possible, vous avez correctement libéré la mémoire.
Étape 2 : Ajout d’un élément
L’action d’ajouter un élément à l’extrémité d’un tableau s’appelle un push. Lorsqu’on veut différentier les ajouts à l’avant ou à l’arrière du tableau on dira push_front et push_back.
Écrivez la fonction intarr_push qui prend en paramètre un pointeur sur structure intarr et un nombre entier. Elle renvoie un nombre entier.
Si il y a de la place dans le tableau (la taille n’a pas atteint la capacité), ajoutez l’élément à la position taille puis incrémentez la taille de 1. Renvoyez 1.
Si la taille est égale à la capacité, refusez d’ajouter l’élément, renvoyez 0.
Étape 3 : somme
Écrivez la fonction intarr_sum. Elle prend en paramètre un pointeur sur structure intarr. Elle doit renvoyer la somme de toutes les valeurs stockées dans le tableau.
Exemple de main de test :
#include <stdio.h>
#include "intarr.h"
int main(void)
{
struct intarr *array;
array = intarr_create(20);
intarr_push(array, 12);
intarr_push(array, 26);
intarr_push(array, 666);
printf("%d\n", intarr_sum(array));
intarr_delete(array);
}
Celui-ci devrait afficher 704.
Étape 4 : moyenne
Écrivez la fonction intarr_avg. Elle prend en paramètre un pointeur sur structure intarr. Elle renvoie la moyenne e toutes les valeurs stockées dans le tableau.