.. _10-structures: Structures et alias =================== :ref:`08-arrays` permettent de manipuler une collection d'objets homogènes, soit directement, soit, c'est préférable voire indispensable, à l'aide de pointeurs. Pour manipuler une collection d'objets inhomogènes, le langage C introduit la notion de structure que l'on définit avec le mot clé ``struct``. Un premier exemple ------------------ Imaginons que l'on souhaite concevoir un programme de gestion d'une librairie pour lequel chaque livre serait défini par les informations suivantes : - titre ; - nom de l'auteur ; - numéro `ISBN `_ ; - prix. Voyons comment implémenter ces données. Déclaration de la structure ........................... Pour stocker cette information, on peut créer une structure ``Book`` de la façon suivante: .. code-block:: c struct Book { char title[100]; char author[50]; long int isbn; double price; }; Chaque information correspond à un champ de la structure et lors de la compilation la mémoire sera réservée conformément au type et à la taille de chacun de ces champs. Dans le cas présent, elle réserve 2 tableaux de ``char`` pour le titre et l'auteur, un ``long int`` pour le numéro ISBN et un ``double`` pour le prix. Pour savoir à quel endroit déclarer cette structure, rappelons les bonnes pratiques de l'organisation d'un programme C. Le code source se décompose en 4 parties: #. Les directives ``include`` et ``define`` ; #. les variables globales ; #. les fonctions secondaires ; #. la fonction principale :func:`main`. Dans le code source, la structure sera donc placée avant les fonctions secondaires, dans l'espace des variables globales. Comme elle est définie en dehors des fonctions, elle sera donc accessible par chacune de ces fonctions. .. _note-long-int: .. note:: Le ``long int`` est ici nécessaire car le simple ``int`` ne permet pas de stocker de très grands nombre. Or le numéro ISBN peut comporter jusqu'à 13 chiffres. Observons les valeurs limites de chacun des deux types, en prêtant attention à l'emplacement réservé pour l'affichage d'un ``long int``. Cette opération nécessite le fichier :file:`limits.h`: .. code-block:: c //limit.c #include #include int main( ) { printf( "Valeur max pour un int : %d\n", INT_MAX); printf( "Valeur max pour un long int : %ld\n", LONG_MAX); return 0; } Le résultat de l'exécution :: $ gcc -std=c99 -Wall -Wextra limit.c -o limit $ ./limit Valeur max pour un int : 2147483647 Valeur max pour un long int : 9223372036854775807 Une fois la structure déclarée, l'étape suivante est de l'initialiser, puis de l'afficher. Initialisation et affichage ........................... Ecrivons une fonction :func:`main` permettant de manipuler cette structure. L'accès aux champs de la structure se fait avec l'opérateur ``.`` : .. code-block:: c int main() { struct Book book1 = {"Le Seigneur des anneaux", "J.R.R. Tolkien", 2266286269, 18.90}; printf("Book 1 title : %s\n", book1.title); printf("Book 1 author : %s\n", book1.author); printf("Book 1 ISBN : %li\n", book1.isbn); printf("Book 1 price : %.2f €\n", book1.price); return 0; } Remarquer le modificateur ``.2`` utilisé pour l'affichage du prix. Il sert à ne conserver que deux chiffres décimaux lors de l'affichage. C'est sans incidence sur sa représentation en mémoire qui continue à utiliser la pleine résolution. .. admonition:: Exercice Utiliser les fragments de code ci dessus pour construire le programme exécutable affichant les caractéristiques du livre de Tolkien dans le terminal:: $ gcc -std=c99 -Wall -Wextra book.c -o book $ ./book Book title : Le Seigneur des anneaux Book author : J.R.R. Tolkien Book ISBN : 2266286269 Book price : 18.90 .. admonition:: Exercice Evaluer la taille d'une instance de la structure ``Book`` ainsi que la taille de chacun de ses champs en intégrant le code ci dessous dans la fonction :func:`main`. ``%zu`` est le modificateur de format pour afficher une taille en octets. .. code-block:: C printf("size of Book structure: %zu bytes\n", sizeof(myBook)); printf(" size of title field: %zu bytes\n", sizeof(myBook.title)); printf(" size of author field: %zu bytes\n", sizeof(myBook.author)); printf(" size of ISBN field: %zu bytes\n", sizeof(myBook.isbn)); printf(" size of price field: %zu bytes\n", sizeof(myBook.price)); Les résultats obtenus sont ils cohérents ? Pour expliciter la réponse, il faut s'intéresser aux offsets obtenus avec la fonction :func:`offsetof` de la bibliothèque ``stddef.h``. Le type ``size_t`` est également défini dans cette bibliothèque. C'est un type entier non signé qui est capable de stocker la taille de n'importe quel objet en octets. .. code-block:: C size_t titleOffset = offsetof(struct Book, title); size_t authorOffset = offsetof(struct Book, author); size_t isbnOffset = offsetof(struct Book, isbn); size_t priceOffset = offsetof(struct Book, price); printf("Offset de title: %zu bytes\n", titleOffset); printf("Offset de author: %zu bytes\n", authorOffset); printf("Offset de isbn: %zu bytes\n", isbnOffset); printf("Offset de price: %zu bytes\n", priceOffset); Quel est le champ concerné par la mémoire réservée mais non utilisée ? .. quiz:: quizz-01 :title: Taille d'une structure Utiliser les résultats ci dessus pour répondre au quizz - ``char title[100]`` occupe :quiz:`{"type":"FB","answer":"100", "size":5}` octets en mémoire - ``char author[50]`` occupe :quiz:`{"type":"FB","answer":"50", "size":5}` octets en mémoire - un ``long int`` occupe :quiz:`{"type":"FB","answer":"8", "size":5}` octets en mémoire - un ``double`` occupe :quiz:`{"type":"FB","answer":"8", "size":5}` octets en mémoire - la structure ``Book`` réserve :quiz:`{"type":"FB","answer":"168", "size":5}` octets en mémoire pour chaque enregistrement - la structure ``Book`` occupe plus de mémoire que strictement nécessaire :quiz:`{"type":"TF","answer":"T"}` - il y a :quiz:`{"type":"FB","answer":"2", "size":5}` octets réservés et non utilisés par le champ :quiz:`{"type":"FB","answer":"author", "size":12}` La raison de cette perte de mémoire est que la mémoire est adressée par blocs de 4 octets pour une plus grande efficacité. Ce mécanisme s'appelle l'alignement mémoire. Structures et fonctions ----------------------- Une structure est un type de variable au même titre qu'un ``int``, un ``double``, un ``char``, un tableau ou un pointeur. Avec une fonction, on peut donc théoriquement utiliser les structures comme : - **argument** de la fonction ; - ou **valeur de retour** de la fonction. .. note:: Cependant on se souvient que les arguments sont passés à une fonction par **copie** de valeurs. Comme pour les tableaux, il est donc extrêmement couteux de passer la structure elle même en argument et il sera préférable d'utiliser plutôt des pointeurs pour localiser celle ci dans la mémoire. Ce point sera abordé dans le prochain paragraphe. Pour illustrer l'utilisation d'une structure comme argument, écrivons la fonction :func:`printBook` qui : - prend en argument une structure de type ``Book`` ; - affiche les champs de la structure ; - et ne retourne rien. .. code-block:: c :linenos: void printBook(struct Book book) { printf("Book title : %s\n", book.title); printf("Book author : %s\n", book.author); printf("Book ISBN : %li\n", book.isbn); printf("Book price : %.2f\n", book.price); } Remarquez la construction ``structure.champ`` utilisée. Lorsqu'on manipule directement une structure, l'accès aux champs de cette structure s'obtient avec l'opérateur ``.``. Son appel, depuis la fonction :func:`main` est immédiat : .. code-block:: c :linenos: int main() { struct Book book1 = {"Le Seigneur des anneaux", "J.R.R. Tolkien", 2266286269, 18.90}; printBook(book1); return 0; } L'exemple ci dessus montre l'utilisation d'une structure comme argument d'une fonction. On préfèrera cependant utiliser un pointeur vers la structure plutôt que la structure elle même pour économiser de la mémoire. Pointeur vers une structure --------------------------- Comme pour les autres types, on peut définir un pointeur vers une structure avec l'opérateur ``*``. Structure en lecture seule .......................... Afin de ne pas gaspiller de mémoire par recopie de valeurs, ré écrivons la fonction :func:`printBook` pour qu'elle prenne en argument un pointeur vers une structure ``Book`` plutôt que la structure elle même : .. code-block:: c void printBook(struct Book *book) { printf("Book title : %s\n", book->title); printf("Book author : %s\n", book->author); printf("Book ISBN : %li\n", book->isbn); printf("Book price : %.2f\n", book->price); } L'accès aux champ d'une structure manipulée avec un pointeur utilise l'opérateur ``->``. Ainsi, la construction ``book->title`` est une écriture abrégée, équivalente à ``(*book).title``. Modification d'une structure ............................ La fonction :func:`printBook` ci dessus utilise la structure en **lecture**. Elle ne modifie aucun champ de celle ci. Mais comme un pointeur vers la structure à modifier est passé en argument, on peut également manipuler la structure en **écriture**, en la modifiant. Ecrivons une fonction :func:`changePrice` qui: - prend en argument un pointeur vers une structure ``Book`` et un pourcentage décimal entre 0 et 100% ; - modifie le prix conformément au pourcentage ; - et ne retourne rien. Les consignes ci dessus permettent d'écrire la signature de la fonction. Le code est trivial : .. code-block:: c void changePrice(struct Book *book, double percent){ book->price *= 1+percent/100; } La fonction est appelée dans :func:`main` de la façon suivante : .. code-block:: C struct Book book2 = {"Game Of Thrones, Le trône de fer", "George R.R. Martin", 2290208876, 22.0}; printBook(&book2); changePrice(&book2, -5); printBook(&book2); Avant l'appel:: Book title : Game Of Thrones, Le trône de fer Book author : George R.R. Martin Book ISBN : 2290208876 Book price : 22.00 Après l'appel de :func:`changePrice`:: Book title : Game Of Thrones, Le trône de fer Book author : George R.R. Martin Book ISBN : 2290208876 Book price : 19.80 Le prix a bien été diminué de 5%. .. admonition:: Exercice Utiliser les fragments de code ci dessus pour construire le programme exécutable produisant l'affichage suivant:: $ gcc -std=c99 -Wall -Wextra book.c -o book $ ./book Book title : Le Seigneur des anneaux Book author : J.R.R. Tolkien Book ISBN : 2266286269 Book price : 18.90 Book title : Game Of Thrones, Le trône de fer Book author : George R.R. Martin Book ISBN : 2290208876 Book price : 22.00 Baisse de prix ! Book title : Game Of Thrones, Le trône de fer Book author : George R.R. Martin Book ISBN : 2290208876 Book price : 20.90 .. #include #include #include typedef struct Book { char *title; char *author; long int isbn; double price; } Book; void setBookFields(Book* book, char* title, char* author, long int isbn, double price){ book->title = title; book->author = author; book->isbn = isbn; book->price = price; } void printBook(Book *book) { printf("Book title : %s\n", book->title); printf("Book author : %s\n", book->author); printf("Book ISBN : %li\n", book->isbn); printf("Book price : %.2f\n", book->price); } void changePrice(Book *book, double percent){ book->price *= 1+percent/100; } int main() { Book book1 = {"Le Seigneur des anneaux", "J.R.R. Tolkien", 2266286269, 18.90}; Book book2 ; setBookFields(&book2, "Game Of Thrones, Le trône de fer", "George R.R. Martin", 2290208876, 22.0); printBook(&book1); printBook(&book2); changePrice(&book2, -5); printBook(&book2); printf("size of book1 struct : %ld bytes\n", sizeof(book1)); printf("size of book2 struct : %ld bytes\n", sizeof(book2)); printf("size of book2.title : %ld bytes\n", strlen(book2.title)); return 0; } Définition de type ------------------ Le code ci dessus est fonctionnel mais présente quelques lourdeurs dans la déclaration des structures qui nécessite deux mots clés : - ``struct`` pour indiquer à C que le type qui va suivre est une structure ; - et le nom de la structure proprement dite. On peut définir un type personnalisé (on dit aussi un alias) avec le mot clé ``typedef`` qui est suivi de deux arguments : - le type pour lequel on veut créer un alias ; - suivi du nom de l'alias. .. note:: Si l'utilisation de ``typedef`` est illustrée dans ce qui va suivre avec une structure, ça fonctionne de manière similaire pour tous les types avec la syntaxe:: typedef type alias; Un alias s'utilise de la façon suivante. On en profite pour manipuler les deux premiers champs comme des pointeurs ``char*`` et non plus comme des tableaux de ``char``. .. code-block:: c typedef struct Book { char *title; char *author; long int isbn; double price; } Book; Ce code suivant indique au compilateur C que chaque fois que l'on rencontrera ``Book`` il faudra le remplacer par ``struct Book {...}``. Et donc : - une déclaration ``struct Book book`` sera remplacée par : - une déclaration ``Book book``. On a modifié deux champs de la structure. Qu'en est il maintenant de la taille de celle ci ? .. quiz:: quizz-02 :title: Taille d'une structure - ``char *title`` occupe :quiz:`{"type":"FB","answer":"8", "size":5}` octets en mémoire - ``char *author`` occupe :quiz:`{"type":"FB","answer":"8", "size":5}` octets en mémoire - un ``long int`` occupe :quiz:`{"type":"FB","answer":"8", "size":5}` octets en mémoire - un ``double`` occupe :quiz:`{"type":"FB","answer":"8", "size":5}` octets en mémoire - la structure ``Book`` réserve donc :quiz:`{"type":"FB","answer":"32", "size":5}` octets en mémoire pour chaque enregistrement La taille est bien inférieure à celle de la première version, celle avec les tableaux de ``char``. Quelle en est la raison ? .. quiz:: quizz-03 :title: Taille d'une structure - :quiz:`{"type":"TF","answer":"F"}` les chaines de caractères ont été compressées ; - :quiz:`{"type":"TF","answer":"T"}` les chaines de caractères sont stockées en dehors de la structure. Pour connaitre la longueur d'une chaine de caractère, on peut faire appel à la fonction :func:`strlen`, dont le prototype est déclaré dans :file:``. Une version simplifiée: .. code-block:: C int strlen(char *str); Utiliser :func:`strlen` pour connaitre la taille du champ *title* et *author* des livres de Tolkien et Martin. .. quiz:: quizz-03 :title: Taille d'une chaine de caractères - Le champ ``title`` du livre *Le Seigneur des anneaux* occupe :quiz:`{"type":"FB","answer":"24", "size":5}` octets en mémoire - Le champ ``author`` du livre *Le Seigneur des anneaux* occupe :quiz:`{"type":"FB","answer":"15", "size":5}` octets en mémoire - Le champ ``title`` du livre *Game Of Thrones* occupe :quiz:`{"type":"FB","answer":"33", "size":5}` octets en mémoire - Le champ ``author`` du livre *Game Of Thrones* occupe :quiz:`{"type":"FB","answer":"19", "size":5}` octets en mémoire Le programme peut ainsi être ré écrit comme ci dessous : .. code-block:: C // book2.c #include typedef struct Book { char *title; char *author; long int isbn; double price; } Book; void printBook(Book *book) { printf("Book title : %s\n", book->title); printf("Book author : %s\n", book->author); printf("Book ISBN : %li\n", book->isbn); printf("Book price : %.2f\n", book->price); } int main() { Book book1 = {"Le Seigneur des anneaux", "J.R.R. Tolkien", 2266286269, 18.90}; Book book2 = {"Game Of Thrones, Le trône de fer", "George R.R. Martin", 2290208876, 22.0}; printBook(&book1); printBook(&book2); return 0; } Tableau de structures --------------------- En C on peut manipuler des tableaux : - de types prédéfinis : ``int``, ``double``, ``char``, etc. ; - de pointeurs : ``int*``, ``double*``, ``char*``, etc. ; - mais également de ``struct``. Illustrons ça en définissant une collection de ``Book`` avec un tableau : .. code-block:: C typedef Book Books[50]; L'instruction ci dessus définit ``Books`` comme un tableau de 50 ``Book``. On peut définir une fonction :func:`setBookFields` pour renseigner un ``Book``: .. code-block:: C void setBookFields(Book* book, char* title, char* author, long int isbn, double price){ book->title = title; book->author = author; book->isbn = isbn; book->price = price; } Quelques questions pour une meilleure compréhension de la fonction : .. quiz:: quizz-04 :title: La fonction setBookFields() - La variable ``book`` utilisée dans la fonction :func:`setBookFields` est de type ``Book`` :quiz:`{"type":"TF","answer":"F"}` - La variable ``book`` utilisée dans la fonction :func:`setBookFields` est de type ``Book*`` :quiz:`{"type":"TF","answer":"T"}` - La variable ``book`` utilisée dans la fonction :func:`setBookFields` est une structure :quiz:`{"type":"TF","answer":"F"}` - La variable ``book`` utilisée dans la fonction :func:`setBookFields` est un pointeur :quiz:`{"type":"TF","answer":"T"}` Utilisons là pour remplir quelques éléments du tableau : .. code-block:: C int main() { Books books; setBookFields(&books[0], "Le Seigneur des anneaux", "J.R.R. Tolkien", 2266286269, 18.90); setBookFields(&books[1], "Game Of Thrones, Le trône de fer", "George R.R. Martin", 2290208876, 22.0); setBookFields(&books[2], "Le Nom de la rose", "Umberto Eco", 2253033138, 8.90); return 0; } .. quiz:: quizz-05 :title: La fonction main() - La variable ``books`` utilisée dans la fonction :func:`main` est de type ``Books`` :quiz:`{"type":"TF","answer":"T"}` - La variable ``books`` utilisée dans la fonction :func:`main` est de type ``Books*`` :quiz:`{"type":"TF","answer":"F"}` - La variable ``books`` utilisée dans la fonction :func:`main` est une structure :quiz:`{"type":"TF","answer":"F"}` - La variable ``books`` utilisée dans la fonction :func:`main` est un pointeur :quiz:`{"type":"TF","answer":"F"}` - La variable ``books`` utilisée dans la fonction :func:`main` est un tableau :quiz:`{"type":"TF","answer":"T"}` L'opérateur d'indexation ``[ ]`` est prioritaire par rapport à l'opérateur d'adressage ``&``, ce qui nous permet d'écrire : - ``&books[0]`` ; - plutôt que ``&(books[0])``, pour une écriture allégée. Si l'on souhaite afficher la collection, on peut écrire une fonction :func:`printBooks` qui fait appel à :func:`printBook`: .. code-block:: C void printBooks(Books *books, int n){ for (int i=0; i