Problème avec typedef
Fermé
rbarraud
-
Modifié le 18 janv. 2022 à 14:21
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 - 18 janv. 2022 à 15:01
mamiemando Messages postés 33446 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 décembre 2024 - 18 janv. 2022 à 15:01
2 réponses
[Dal]
Messages postés
6198
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
13 décembre 2024
1 096
Modifié le 6 janv. 2022 à 22:05
Modifié le 6 janv. 2022 à 22:05
Salut rbarraud,
Chez moi ce code compile sans erreurs ni warnings avec
Comme tu n'as pas fournit Sceau.h son include est commenté dans ce code.
Note qu'il est une bonne pratique de mettre des "header guards" dans les fichiers .h, afin d'éviter les erreurs liées à des inclusions multiples.
Le listSceau.h pourrait dès lors se présenter comme ceci :
Note quand même que les lignes 10 à 18 ci-dessus sont vraiment horribles :
l'ensemble a pour résultat de tromper l'utilisateur ou le lecteur , et toi même éventuellement... et un constitue un abus de la flexibilité permise par la syntaxe du langage C.
Dal
Chez moi ce code compile sans erreurs ni warnings avec
gcc -Wall -Wextra main.c, un main contenant le code qui suit, et le listSceau.h tel que tu l'as posté :
#include <stdlib.h> //#include "Sceau.h" #include "listSceau.h" #define TAILLE_MAX 10 #define TAILLE_NOMBRE 4 int main(void) { listSceau ls; Sceau l0 = NULL; /* do something with ls and l0 */ (void) ls; (void) l0; return 0; }
Comme tu n'as pas fournit Sceau.h son include est commenté dans ce code.
Note qu'il est une bonne pratique de mettre des "header guards" dans les fichiers .h, afin d'éviter les erreurs liées à des inclusions multiples.
Le listSceau.h pourrait dès lors se présenter comme ceci :
#ifndef LISTSCEAU_H #define LISTSCEAU_H typedef struct ElementSceau { char *nombre; struct ElementSceau *suivant; }ElementSceau; /* Définition d'une Sceaue */ typedef struct Sceau { int taille; struct ElementSceau *debut; struct ElementSceau *fin; }*Sceau; typedef Sceau listSceau[16]; #endif /* LISTSCEAU_H */
Note quand même que les lignes 10 à 18 ci-dessus sont vraiment horribles :
- le typedef ligne 11 masque un pointeur vers un type struct qui porte le même nom que l'étiquette de la struct
- le typedef en ligne 18 n'a aucun sens pour moi ... tu définis listSceau[16] comme un alias de Sceau qui est lui même un pointeur sur struct Sceau....
l'ensemble a pour résultat de tromper l'utilisateur ou le lecteur , et toi même éventuellement... et un constitue un abus de la flexibilité permise par la syntaxe du langage C.
Dal
mamiemando
Messages postés
33446
Date d'inscription
jeudi 12 mai 2005
Statut
Modérateur
Dernière intervention
20 décembre 2024
7 812
Modifié le 18 janv. 2022 à 15:02
Modifié le 18 janv. 2022 à 15:02
Bonjour,
Par rapport à ton code
Au delà de la fonction main incomplète et des fichiers manquants, ton code est étrange. On dirait que tu mélanges l'implémentation d'une liste chaînée et celle d'un tableau.
Plus simplement, tes sceaux sont justes des entiers, peut être que manipuler un tableau d'entiers serait suffisant dans ton cas.
Souvent, en C, on nomme les types plutôt selon le format
Ici je suis les recommandations de [Dal], qui que "le typedef ligne 11 masque un pointeur vers un type struct qui porte le même nom que l'étiquette de la struct". En fait, ça n'est pas gênant (c'est d'ailleurs un alias fait implicitement en C++) mais c'est vrai que souvent on distingue les deux. Personnellement je n'ai donc aucun complexe à écrire :
Liste ou tableaux ?
Il est important de souligner qu'une liste chaînée a ses intérêts, mais force de toute façon a maîtriser des fonctions comme
Ici on obtient un tableau de 15 éléments de type
Alors, liste ou tableau ?
Bref, il est important de bien choisir la structure de donnée adaptée pour avoir un programme plus efficace.
Les listes chaînées
Généralement, on utilise une structure qui modélise un maillon de la liste. Selon que la liste est simplement chaînée ou doublement chaînée, on stocke dans chaque maillon un pointeur vers l'éventuel maillon suivant et (dans le cas d'une liste doublement chaînée) vers le maillon précédent. Ces pointeurs doivent être mis à jour par les primitives qui implémentent la structure de liste chaînée (ajout, suppression d'un maillon).
Avec une telle implémentation, on impose que les attributs
Chaque maillon stocke en plus soit directement la donnée du maillon (e.g.,
À ce stade, on pourrait tout à fait décréter avec un
... ou à une adresse de maillon (donc
Ce choix n'a pas vraiment d'importance, mais ça impactera le reste du code car un pointeur et une structure ne se manipulent pas de la même façon (selon ce choix, pour accéder à un attribut, on utilise
Mais souvent, on définit une structure auxiliaire qui va permettre d'avoir le pointeur sur le premier et le dernier élément. Son principal intérêt est surtout d'accéder en O(1) au dernier élément donc c'est en fait assez anecdotique. Dans la même veine, tu pourrais aussi décider d'y stocker la taille de la liste. L'autre gros intérêt de bien distinguer ces deux structures, c'est qu'on ne risque pas de passer par erreur à une fonction l'adresse d'un maillon quand il faut sémantiquement parlant passer l'adresse d'une liste (le compilateur te le dira, ce qui ne serait pas le cas avec un
Si tu veux voir une implémentation possible de liste chaînée générique, tu peux par exemple regarder ces deux fichiers :
Bonne chance
Par rapport à ton code
Au delà de la fonction main incomplète et des fichiers manquants, ton code est étrange. On dirait que tu mélanges l'implémentation d'une liste chaînée et celle d'un tableau.
- Le nommage est améliorable : ce que tu appelles
struct Sceau
devrait s'appeler logiquementstruct ListeSceau
; -
typedef Sceau listSceau[16];
n'a pas de sens :- soit tu veux déclarer une variable globale nommée listSceau qui fait la taille de 16 instances de type Sceau, et dans ce cas il faut juste écrire
Sceau listSceau[16
]; - soit tu veux définir un type qui permet de nommer un tableau statique de 16 objets de type
Sceau
(ce qui est une mauvaise idée) il aurait fallu écriretypedef Sceau[16] listSceau
.
- soit tu veux déclarer une variable globale nommée listSceau qui fait la taille de 16 instances de type Sceau, et dans ce cas il faut juste écrire
Plus simplement, tes sceaux sont justes des entiers, peut être que manipuler un tableau d'entiers serait suffisant dans ton cas.
Souvent, en C, on nomme les types plutôt selon le format
ma_structure_t(le style camel case étant plutôt utilisé en Java et parfois en C++) ce qui donne :
typedef struct _ma_structure_t { // ... } ma_structure_t;
Ici je suis les recommandations de [Dal], qui que "le typedef ligne 11 masque un pointeur vers un type struct qui porte le même nom que l'étiquette de la struct". En fait, ça n'est pas gênant (c'est d'ailleurs un alias fait implicitement en C++) mais c'est vrai que souvent on distingue les deux. Personnellement je n'ai donc aucun complexe à écrire :
typedef struct ma_structure_t { // ... } ma_structure_t;
Liste ou tableaux ?
Il est important de souligner qu'une liste chaînée a ses intérêts, mais force de toute façon a maîtriser des fonctions comme
mallocet
free. À partir du moment où tu les maîtrises, tu peux aussi considérer des tableaux alloués dynamiquement. Cela signifie en particulier que tu ne manipules plus que des pointeurs (donc plus besoin de définir des types avec une taille codée en dur).
#include <stdlib.h> #include <stdio.h> typedef struct ma_structure_t { int attribut1; char attribut2; } ma_structure_t; int main(){ unsigned n = 15; ma_structure_t * mes_structures = (ma_structure_t *) malloc(n * sizeof(ma_structure_t)); if (mes_structures) { // ... free(mes_structures); } else { fprintf(stderr, "Pas assez de mémoire"); return 1; } }
Ici on obtient un tableau de 15 éléments de type
ma_structure_talloué dynamiquement (ce qui signifie que la valeur de
npeut être inconnue à la compilation et n'être connue qu'à l'exécution (typiquement parce qu'elle est spécifiée par l'utilisateur).
Alors, liste ou tableau ?
- Une liste est bien adaptée si tu as fréquemment des éléments à supprimer indépendamment de leur positions, mais si par exemple tu ne supprimes que le premier ou le dernier élément, tu peux faire beaucoup plus simple avec des tableaux. Il est alors judicieux d'envelopper le tableau dans une structure qui indique l'adresse de début et l'adresse de fin et qui déclenche une réallocation en cas de besoin.
- Pour un tableau, le temps d'accès à un élément se fait en O(1) quelle que soit la taille du tableau.
- Pour une liste, le temps d'accès à un élément se fait en O(n).
Bref, il est important de bien choisir la structure de donnée adaptée pour avoir un programme plus efficace.
Les listes chaînées
Généralement, on utilise une structure qui modélise un maillon de la liste. Selon que la liste est simplement chaînée ou doublement chaînée, on stocke dans chaque maillon un pointeur vers l'éventuel maillon suivant et (dans le cas d'une liste doublement chaînée) vers le maillon précédent. Ces pointeurs doivent être mis à jour par les primitives qui implémentent la structure de liste chaînée (ajout, suppression d'un maillon).
typedef struct maillon_t { //struct maillon_t * precedent; struct maillon_t * suivant; int donnee; } maillon_t;
Avec une telle implémentation, on impose que les attributs
suivant(resp.
precedentsont initialisés à
NULLquand il n'y a pas d'élément suivant (resp. précédent).
Chaque maillon stocke en plus soit directement la donnée du maillon (e.g.,
int donnee), soit un pointeur vers cette donnée. Ainsi, une liste chaînée générique est une liste d'adresse mémoire générique (de type
void *).
typedef struct maillon_t { //struct maillon_t * precedent; struct maillon_t * suivant; void * donnee; } maillon_t;
À ce stade, on pourrait tout à fait décréter avec un
typedefque le type
liste_tcorrespond exactement au type
struct maillon_t.
typedef struct maillon_t liste_t;
... ou à une adresse de maillon (donc
struct maillon_t *) :
typedef struct maillon_t * liste_t;
Ce choix n'a pas vraiment d'importance, mais ça impactera le reste du code car un pointeur et une structure ne se manipulent pas de la même façon (selon ce choix, pour accéder à un attribut, on utilise
ma_structure.mon_attributou
pointeur_structure->mon_attribut.
Mais souvent, on définit une structure auxiliaire qui va permettre d'avoir le pointeur sur le premier et le dernier élément. Son principal intérêt est surtout d'accéder en O(1) au dernier élément donc c'est en fait assez anecdotique. Dans la même veine, tu pourrais aussi décider d'y stocker la taille de la liste. L'autre gros intérêt de bien distinguer ces deux structures, c'est qu'on ne risque pas de passer par erreur à une fonction l'adresse d'un maillon quand il faut sémantiquement parlant passer l'adresse d'une liste (le compilateur te le dira, ce qui ne serait pas le cas avec un
typedef struct maillon_t liste_t;). Bref, la structure peut être définie ainsi :
typedef struct liste_t { maillon_t * debut; maillon_t * fin; unsigned taille; } liste_t;
Si tu veux voir une implémentation possible de liste chaînée générique, tu peux par exemple regarder ces deux fichiers :
Bonne chance