Pourquoi devrions-nous taper si souvent une structure en C?

407

J'ai vu de nombreux programmes composés de structures comme celle ci-dessous

typedef struct 
{
    int i;
    char k;
} elem;

elem user;

Pourquoi est-il si nécessaire? Une raison spécifique ou un domaine applicable?

Manoj Doubts
la source
14
Réponse plus complète et précise: stackoverflow.com/questions/612328/…
AbiusX
Il a des inconvénients, je pense que vous ne pouvez pas créer une liste de liens avec une structure anonyme car la ligne struct * ptrà l'intérieur de la structure provoquera une erreur
Raghib Ahsan
9
La `` réponse la plus approfondie et la plus précise '' est Différence entre struct et struct typedef en C ++ , et il existe des différences significatives entre C et C ++ dans ce domaine qui rendent cette réponse pas tout à fait appropriée pour une question sur C.
Jonathan Leffler
Cette question a une définition de struct vs typedef en double, ainsi que des réponses stellaires.
Jonathan Leffler
2
OTOH, kernel.org/doc/html/v4.10/process/coding-style.html nous dit que nous ne devrions pas faire de tels typedefs.
glglgl

Réponses:

455

Comme l'a dit Greg Hewgill, le typedef signifie que vous n'avez plus à écrire structpartout. Cela permet non seulement d'économiser les frappes, mais aussi de rendre le code plus propre car il fournit une abstraction plus smidgen.

Choses comme

typedef struct {
  int x, y;
} Point;

Point point_new(int x, int y)
{
  Point a;
  a.x = x;
  a.y = y;
  return a;
}

devient plus propre lorsque vous n'avez pas besoin de voir le mot-clé "struct" partout, il semble plutôt qu'il y ait vraiment un type appelé "Point" dans votre langue. Ce qui, après le typedef, est le cas je suppose.

Notez également que même si votre exemple (et le mien) a omis de nommer le struct lui - même, le nommer est également utile lorsque vous souhaitez fournir un type opaque. Ensuite, vous auriez du code comme celui-ci dans l'en-tête, par exemple:

typedef struct Point Point;

Point * point_new(int x, int y);

puis fournissez la structdéfinition dans le fichier d'implémentation:

struct Point
{
  int x, y;
};

Point * point_new(int x, int y)
{
  Point *p;
  if((p = malloc(sizeof *p)) != NULL)
  {
    p->x = x;
    p->y = y;
  }
  return p;
}

Dans ce dernier cas, vous ne pouvez pas renvoyer la valeur Point par, car sa définition est masquée aux utilisateurs du fichier d'en-tête. C'est une technique largement utilisée dans GTK + , par exemple.

MISE À JOUR Notez qu'il existe également des projets C très appréciés où cette utilisation de typedefto hide structest considérée comme une mauvaise idée, le noyau Linux est probablement le projet le plus connu. Voir le chapitre 5 du document Linux Kernel CodingStyle pour les mots énervés de Linus. :) Mon point est que le "devrait" dans la question n'est peut-être pas gravé dans le marbre, après tout.

se détendre
la source
58
Vous ne devez pas utiliser d'identifiants avec un trait de soulignement suivi d'une lettre majuscule, ils sont réservés (voir section 7.1.3 paragraphe 1). Bien qu'il soit peu probable qu'il s'agisse d'un problème majeur, il s'agit d'un comportement techniquement indéfini lorsque vous les utilisez (7.1.3 paragraphe 2).
dreamlax
16
@dreamlax: Au cas où cela ne serait pas clair pour les autres, cela ne fait que commencer un identifiant avec un trait de soulignement et un majuscule que vous ne devriez pas faire; vous êtes libre de l'utiliser au milieu d'un identifiant.
brianmearns
12
Il est intéressant de noter que l'exemple donné ici (dans lequel le typedef empêche d'utiliser "struct" "partout") est en réalité plus long que le même code sans le typedef, car il enregistre exactement une utilisation du mot "struct". Le smidgen d'abstraction gagné se compare rarement favorable à l'obfuscation supplémentaire.
William Pursell
7
@Rerito fyi, page 166 du brouillon C99 , Tous les identifiants qui commencent par un trait de soulignement et soit une lettre majuscule ou un autre trait de soulignement sont toujours réservés à toute utilisation. Et tous les identifiants qui commencent par un trait de soulignement sont toujours réservés pour être utilisés comme identifiants avec une portée de fichier dans les espaces de nom ordinaire et de balise.
e2-e4
10
Chose intéressante, les directives de codage du noyau Linux disent que nous devrions être beaucoup plus conservateurs quant à l'utilisation des typedefs (section 5): kernel.org/doc/Documentation/CodingStyle
gsingh2011
206

C'est incroyable combien de personnes se trompent. S'IL VOUS PLAÎT ne pas typedef structs en C, il pollue inutilement l'espace de noms global qui est généralement très pollué déjà dans les grands programmes C.

En outre, les structures typedef'd sans nom de balise sont une cause majeure d'imposition inutile de relations de classement entre les fichiers d'en-tête.

Considérer:

#ifndef FOO_H
#define FOO_H 1

#define FOO_DEF (0xDEADBABE)

struct bar; /* forward declaration, defined in bar.h*/

struct foo {
  struct bar *bar;
};

#endif

Avec une telle définition, sans utiliser de typedefs, il est possible pour une unité compiland d'inclure foo.h pour obtenir la FOO_DEFdéfinition. S'il ne tente pas de déréférencer le membre "bar" de la foostructure, il ne sera pas nécessaire d'inclure le fichier "bar.h".

De plus, comme les espaces de noms sont différents entre les noms de balises et les noms de membres, il est possible d'écrire du code très lisible tel que:

struct foo *foo;

printf("foo->bar = %p", foo->bar);

Étant donné que les espaces de noms sont séparés, il n'y a pas de conflit dans les noms de variables coïncidant avec leur nom de balise struct.

Si je dois conserver votre code, je supprimerai vos structures typedef'd.

Jerry Hicks
la source
37
Ce qui est plus étonnant, c'est que 13 mois après que cette réponse ait été donnée, je suis le premier à la voter! typedef'ing structs est l'un des plus grands abus de C, et n'a pas sa place dans un code bien écrit. typedef est utile pour désobscurcir les types de pointeurs de fonction compliqués et ne sert vraiment à rien d'autre.
William Pursell
28
Peter van der Linden plaide également contre les structures typographiques dans son livre éclairant "Expert C Programming - Deep C Secrets". L'essentiel est: vous VOULEZ savoir que quelque chose est une structure ou une union, pas le CACHER.
Jens
34
Le style de codage du noyau Linux interdit explicitement les structures de typedefing. Chapitre 5: Typedefs: "C'est une erreur d'utiliser typedef pour les structures et les pointeurs." kernel.org/doc/Documentation/CodingStyle
jasso
63
Quels sont les avantages, exactement, de taper "struct" encore et encore? Et en parlant de pollution, pourquoi voudriez-vous avoir une structure et une fonction / variable / typedef avec le même nom dans un espace de noms global (sauf s'il s'agit d'un typedef pour cette même fonction)? Le modèle sûr est à utiliser typedef struct X { ... } X. De cette façon, vous pouvez utiliser le formulaire court Xpour adresser la structure partout où la définition est disponible, mais vous pouvez toujours la déclarer et l'utiliser struct Xà votre guise.
Pavel Minaev
7
Je utilise très rarement, voire jamais, typedef, je ne dirais pas que les autres ne devraient pas l'utiliser, ce n'est tout simplement pas mon style. J'aime voir une structure avant un type de variable, donc je sais tout de suite que c'est une structure. L'argument le plus facile à taper est un peu boiteux, avoir des variables qui sont une seule lettre est également plus facile à taper, également avec les complétions automatiques à quel point est-il difficile de taper struct de nos jours n'importe où.
Nathan Day
138

Extrait d'un ancien article de Dan Saks ( http://www.ddj.com/cpp/184403396?pgno=3 ):


Les règles du langage C pour nommer les structures sont un peu excentriques, mais elles sont assez inoffensives. Cependant, lorsqu'elles sont étendues aux classes en C ++, ces mêmes règles ouvrent de petites fissures pour les bogues à parcourir.

En C, les noms apparaissant dans

struct s
    {
    ...
    };

est une balise. Un nom de balise n'est pas un nom de type. Compte tenu de la définition ci-dessus, des déclarations telles que

s x;    /* error in C */
s *p;   /* error in C */

sont des erreurs en C. Vous devez les écrire comme

struct s x;     /* OK */
struct s *p;    /* OK */

Les noms des unions et des énumérations sont également des balises plutôt que des types.

En C, les balises sont distinctes de tous les autres noms (pour les fonctions, les types, les variables et les constantes d'énumération). Les compilateurs C conservent les balises dans une table de symboles conceptuellement sinon physiquement séparée de la table contenant tous les autres noms. Ainsi, il est possible pour un programme C d'avoir à la fois une balise et un autre nom avec la même orthographe dans la même portée. Par exemple,

struct s s;

est une déclaration valide qui déclare la variable s de type struct s. Ce n'est peut-être pas une bonne pratique, mais les compilateurs C doivent l'accepter. Je n'ai jamais vu de raison pour laquelle C a été conçu de cette façon. J'ai toujours pensé que c'était une erreur, mais ça y est.

De nombreux programmeurs (y compris le vôtre) préfèrent considérer les noms de structure comme des noms de type, ils définissent donc un alias pour la balise à l'aide d'un typedef. Par exemple, définir

struct s
    {
    ...
    };
typedef struct s S;

vous permet d'utiliser S à la place de struct s, comme dans

S x;
S *p;

Un programme ne peut pas utiliser S comme nom d'un type et d'une variable (ou fonction ou constante d'énumération):

S S;    // error

C'est bon.

Le nom de balise dans une définition de structure, d'union ou d'énumération est facultatif. De nombreux programmeurs replient la définition de la structure dans le typedef et se dispensent complètement de la balise, comme dans:

typedef struct
    {
    ...
    } S;

L'article lié a également une discussion sur la façon dont le comportement C ++ de ne pas exiger un typedefpeut provoquer des problèmes de masquage de nom subtils. Pour éviter ces problèmes, c'est aussi une bonne idée pour typedefvos classes et structures en C ++, même si à première vue cela semble inutile. En C ++, typedefle nom caché devient une erreur dont le compilateur vous parle plutôt qu'une source cachée de problèmes potentiels.

Michael Burr
la source
2
Un exemple où le nom de la balise est identique à un nom sans balise se trouve dans le programme (POSIX ou Unix) avec la int stat(const char *restrict path, struct stat *restrict buf)fonction. Là, vous avez une fonction statdans l'espace de nom ordinaire et struct statdans l'espace de nom de balise.
Jonathan Leffler
2
votre déclaration, SS; // erreur .... EST MAUVAIS Cela fonctionne bien. Je veux dire que votre déclaration selon laquelle "nous ne pouvons pas avoir le même nom pour la balise typedef et var" est erronée ...
veuillez
63

L'utilisation de a typedefévite d'avoir à écrire à structchaque fois que vous déclarez une variable de ce type:

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct
Greg Hewgill
la source
2
ok nous n'avons pas ce problème en C ++. Alors, pourquoi ne pas supprimer ce problème du compilateur C et le rendre identique à C ++. Ok C ++ a des domaines d'application différents et il a donc les fonctionnalités avancées.Mais ne pouvons-nous pas hériter certains d'entre eux en C sans changer le C d'origine?
Manoj Doubts
4
Manoj, le nom de la balise ("struct foo") est nécessaire lorsque vous devez définir une structure qui se référence elle-même. par exemple le pointeur "suivant" dans une liste chaînée. Plus précisément, le compilateur implémente la norme, et c'est ce que la norme dit de faire.
Michael Carman
41
Ce n'est pas un problème dans le compilateur C, cela fait partie de la conception. Ils ont changé cela pour C ++, ce qui, je pense, facilite les choses, mais cela ne signifie pas que le comportement de C est mauvais.
Herms
5
malheureusement, de nombreux «programmeurs» définissent une structure puis la typent avec un nom «non lié» (comme struct myStruct ...; typedef struct myStruct susan *; Dans presque tous les cas, une typedef ne mène à rien d'autre qu'à encombrer le code, cachant la définition réelle de une variable / paramètre, et induit en
erreur
1
@ user3629249 Je suis d'accord avec vous que le style de programmation mentionné est horrible mais ce n'est pas une raison pour dénigrer typedefles structures en général. Vous pourriez aussi le faire typedef struct foo foo;. Bien sûr, le structmot - clé n'est pas requis plus que cela, ce qui peut être un indice utile que l'alias de type que nous regardons est un alias pour une structure mais ce n'est pas mauvais en général. Considérons également un cas dans lequel l'identifiant de l'alias de type résultant de typedefindique qu'il est un alias pour une structure, fe: typedef struct foo foo_struct;.
RobertS soutient Monica Cellio le
38

Une autre bonne raison de toujours taper les énumérations et les structures résulte de ce problème:

enum EnumDef
{
  FIRST_ITEM,
  SECOND_ITEM
};

struct StructDef
{
  enum EnuumDef MyEnum;
  unsigned int MyVar;
} MyStruct;

Remarquez la faute de frappe dans EnumDef dans la structure (Enu u mDef)? Ceci se compile sans erreur (ou avertissement) et est (selon l'interprétation littérale de la norme C) correct. Le problème est que je viens de créer une nouvelle définition d'énumération (vide) dans ma structure. Je n'utilise pas (comme prévu) la définition précédente EnumDef.

Avec un typdef, des fautes de frappe similaires auraient entraîné des erreurs de compilation pour l'utilisation d'un type inconnu:

typedef 
{
  FIRST_ITEM,
  SECOND_ITEM
} EnumDef;

typedef struct
{
  EnuumDef MyEnum; /* compiler error (unknown type) */
  unsigned int MyVar;
} StructDef;
StrructDef MyStruct; /* compiler error (unknown type) */

Je recommanderais TOUJOURS de taper des structures et des énumérations.

Non seulement pour économiser de la frappe (sans jeu de mots;)), mais parce que c'est plus sûr.

cschol
la source
1
Pire encore, votre faute de frappe peut coïncider avec une balise différente. Dans le cas d'une structure, cela pourrait entraîner la compilation correcte du programme entier et un comportement indéfini lors de l'exécution.
MM
3
cette définition: 'typedef {FIRST_ITEM, SECOND_ITEM} EnumDef;' ne définit pas une énumération. J'ai écrit des centaines de programmes énormes et j'ai eu la malchance d'effectuer la maintenance de programmes que d'autres ont écrits. De la dure expérience, l'utilisation de typedef sur une structure ne pose que des problèmes. Espérons que le programmeur ne soit pas tellement handicapé qu'il a du mal à taper une définition complète lorsqu'il déclare une instance de structure. C n'est pas basique, donc taper quelques caractères supplémentaires n'est pas préjudiciable au fonctionnement du programme.
user3629249
2
Pour ceux qui ont horreur de taper plus que le nombre minimum absolu de caractères, puis-je suggérer de rejoindre un groupe qui essaie d'écrire des applications avec le nombre minimum de touches. N'utilisez simplement pas leur nouvelle «compétence» dans un environnement de travail, en particulier un environnement de travail qui effectue rigoureusement des évaluations par les pairs
user3629249
Remarque, il est généralement déconseillé de l'utiliser enumcomme type, car de nombreux compilateurs émettent d'étranges avertissements lorsqu'ils les utilisent `` mal ''. Par exemple, l'initialisation d'un enumà 0 peut donner un avertissement «constante entière non enum». La déclaration en aval d'un enumn'est pas non plus autorisée. On devrait utiliser int(ou unsigned int) à la place.
yyny
5
Cet exemple ne se compile pas et je ne m'y attendrais pas. Compilation Debug / test.o test.c: 10: 17: erreur: le champ a un type incomplet 'enum EnuumDef' enum EnuumDef MyEnum; ^ test.c: 10: 8: note: déclaration avant de 'enum EnuumDef' enum EnuumDef MyEnum; ^ 1 erreur générée. gnuc, avec std = c99.
natersoz
30

Le style de codage du noyau Linux Le chapitre 5 présente de grands avantages et inconvénients (principalement des inconvénients) de l'utilisation typedef.

Veuillez ne pas utiliser des choses comme "vps_t".

C'est une erreur d'utiliser typedef pour les structures et les pointeurs. Quand vous voyez un

vps_t a;

dans la source, qu'est-ce que cela signifie?

En revanche, si elle dit

struct virtual_container *a;

vous pouvez réellement dire ce qu'est un "a".

Beaucoup de gens pensent que les typedefs "aident à la lisibilité". Mais non. Ils ne sont utiles que pour:

(a) des objets totalement opaques (où le typedef est activement utilisé pour cacher ce qu'est l'objet).

Exemple: "pte_t" etc. objets opaques auxquels vous ne pouvez accéder qu'en utilisant les fonctions d'accesseur appropriées.

REMARQUE! L'opacité et les «fonctions d'accesseur» ne sont pas bonnes en soi. La raison pour laquelle nous les avons pour des choses comme pte_t etc. est qu'il n'y a vraiment absolument aucune information accessible de manière portable.

(b) Effacer les types entiers, où l'abstraction permet d' éviter toute confusion, qu'elle soit "int" ou "longue".

u8 / u16 / u32 sont des caractères typographiques parfaitement fins, bien qu'ils s'inscrivent mieux dans la catégorie (d) qu'ici.

REMARQUE! Encore une fois - il doit y avoir une raison à cela. Si quelque chose est "non signé longtemps", alors il n'y a aucune raison de le faire

typedef unsigned long myflags_t;

mais s'il existe une raison claire pour laquelle, dans certaines circonstances, il peut s'agir d'un "entier non signé" et dans d'autres configurations, il peut être "non signé long", alors allez-y et utilisez un typedef.

(c) lorsque vous utilisez clairsemé pour créer littéralement un nouveau type pour la vérification de type.

d) Nouveaux types identiques aux types C99 standard, dans certaines circonstances exceptionnelles.

Bien qu'il ne faudrait que peu de temps aux yeux et au cerveau pour s'habituer aux types standard comme «uint32_t», certaines personnes s'opposent quand même à leur utilisation.

Par conséquent, les types 'u8 / u16 / u32 / u64' spécifiques à Linux et leurs équivalents signés qui sont identiques aux types standard sont autorisés - bien qu'ils ne soient pas obligatoires dans votre nouveau code.

Lorsque vous modifiez un code existant qui utilise déjà l'un ou l'autre ensemble de types, vous devez vous conformer aux choix existants dans ce code.

(e) Types sûrs pour une utilisation dans l'espace utilisateur.

Dans certaines structures qui sont visibles pour l'espace utilisateur, nous ne pouvons pas exiger de types C99 et ne pouvons pas utiliser le formulaire 'u32' ci-dessus. Ainsi, nous utilisons __u32 et des types similaires dans toutes les structures partagées avec l'espace utilisateur.

Il y a peut-être d'autres cas aussi, mais la règle devrait être de ne JAMAIS utiliser un typedef à moins que vous ne puissiez clairement correspondre à l'une de ces règles.

En général, un pointeur ou une structure contenant des éléments auxquels on peut raisonnablement accéder directement ne doit jamais être un typedef.

Yu Hao
la source
4
«L'opacité et les« fonctions d'accesseur »ne sont pas bonnes en soi». Quelqu'un peut-il expliquer pourquoi? Je pense que la dissimulation et l'encapsulation d'informations seraient une très bonne idée.
Yawar
5
@Yawar Je viens de lire ce document et j'ai exactement la même pensée. Bien sûr, C n'est pas orienté objet, mais l'abstraction est toujours une chose.
Baldrickk
12

Il s'avère qu'il y a des avantages et des inconvénients. Une source d'information utile est le livre fondateur "Expert C Programming" ( Chapitre 3 ). En bref, en C, vous avez plusieurs espaces de noms: balises, types, noms de membres et identificateurs . typedefintroduit un alias pour un type et le localise dans l'espace de noms des balises. À savoir,

typedef struct Tag{
...members...
}Type;

définit deux choses. Une balise dans l'espace de noms de balise et un type dans l'espace de noms de type. Vous pouvez donc faire les deux Type myTypeet struct Tag myTagType. Les déclarations comme struct Type myTypeou Tag myTagTypesont illégales. De plus, dans une déclaration comme celle-ci:

typedef Type *Type_ptr;

nous définissons un pointeur sur notre type. Donc, si nous déclarons:

Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;

puis var1, var2et myTagType1sont des pointeurs vers Type mais myTagType2pas.

Dans le livre mentionné ci-dessus, il mentionne que les structures typedefing ne sont pas très utiles car cela évite au programmeur d'écrire le mot struct. Cependant, j'ai une objection, comme beaucoup d'autres programmeurs C. Bien que cela tourne parfois pour obscurcir certains noms (c'est pourquoi il n'est pas recommandé dans les grandes bases de code comme le noyau) lorsque vous souhaitez implémenter le polymorphisme en C, cela aide beaucoup de gens à chercher ici des détails . Exemple:

typedef struct MyWriter_t{
    MyPipe super;
    MyQueue relative;
    uint32_t flags;
...
}MyWriter;

tu peux faire:

void my_writer_func(MyPipe *s)
{
    MyWriter *self = (MyWriter *) s;
    uint32_t myFlags = self->flags;
...
}

Vous pouvez donc accéder à un membre externe ( flags) par la structure interne ( MyPipe) via la conversion. Pour moi, il est moins déroutant de convertir le type entier que de le faire (struct MyWriter_ *) s;chaque fois que vous souhaitez effectuer une telle fonctionnalité. Dans ces cas, un bref référencement est un gros problème, surtout si vous utilisez fortement la technique dans votre code.

Enfin, le dernier aspect des typedeftypes ed est l'incapacité de les étendre, contrairement aux macros. Si par exemple, vous avez:

#define X char[10] or
typedef char Y[10]

vous pouvez alors déclarer

unsigned X x; but not
unsigned Y y;

Nous ne nous soucions pas vraiment de cela pour les structures, car cela ne s'applique pas aux spécificateurs de stockage ( volatileet const).

user1533288
la source
1
MyPipe *s; MyWriter *self = (MyWriter *) s;et vous venez de casser l'aliasing strict.
Jonathon Reinhart
@JonathonReinhart Il serait illustratif de mentionner comment cela peut être évité, par exemple comment le GTK + extrêmement content de fonctionner fonctionne autour de lui: bugzilla.gnome.org/show_bug.cgi?id=140722 / mail.gnome.org/archives/gtk -devel-list / 2004-April / msg00196.html
underscore_d
"typedef introduit un alias pour un type et le localise dans l'espace de noms des balises. A savoir" typedef struct Tag{ ...members... }Type; "définit deux choses" n'a pas de sens. si typedef définit des balises, «Type» ici devrait également être une balise. La vérité est la définition définit 2 balises et 1 type (ou 2 types et 1 étiquette ne sais pas.): struct Tag, TagEt Type. struct Tagest définitivement un type. Tagest une balise. mais la confusion est de savoir s'il Types'agit d'une balise ou d'un type
Qwertyzw
12

Je ne pense pas que des déclarations prospectives soient même possibles avec typedef. L'utilisation de struct, enum et union permet de transmettre des déclarations lorsque les dépendances (connaissent) sont bidirectionnelles.

Style: L'utilisation de typedef en C ++ est assez logique. Cela peut presque être nécessaire lorsqu'il s'agit de modèles qui nécessitent des paramètres multiples et / ou variables. Le typedef aide à garder la dénomination droite.

Ce n'est pas le cas dans le langage de programmation C. Le plus souvent, l'utilisation de typedef n'a d'autre but que d'obscurcir l'utilisation de la structure de données. Étant donné que seuls {struct (6), enum (4), union (5)} nombre de touches sont utilisés pour déclarer un type de données, il n'y a presque aucune utilité pour l'aliasing de la structure. Ce type de données est-il une union ou une structure? L'utilisation de la déclaration non typée simple vous permet de savoir immédiatement de quel type il s'agit.

Remarquez comment Linux est écrit en évitant strictement cet aliasing de non-sens typedef. Le résultat est un style minimaliste et épuré.

natersoz
la source
10
Nettoyer ne se répéterait pas structpartout ... Typedef crée de nouveaux types. Qu'est ce que tu utilises? Les types. Peu importe qu'il s'agisse d'une structure, d'une union ou d'une énumération, c'est pourquoi nous l'avons typé.
GManNickG
13
Non, nous ne soucions si c'est un struct ou union, par rapport à un certain type ENUM ou atomique. Vous ne pouvez pas contraindre une structure à un entier ou à un pointeur (ou à tout autre type, d'ailleurs), ce qui est tout ce que vous avez parfois pour stocker un certain contexte. La présence des mots clés «struct» ou «union» améliore la localisation du raisonnement. Personne ne dit que vous devez savoir ce qu'il y a à l' intérieur de la structure.
Bernd Jendrissek
1
@BerndJendrissek: Les structures et les unions sont différentes des autres types, mais le code client devrait-il se soucier de laquelle de ces deux choses (struct ou union) quelque chose comme un FILEest?
supercat
4
@supercat FILE est une bonne utilisation de typedef. Je pense que typedef est surutilisé , pas que ce soit une fausse caractéristique de la langue. À mon humble avis, l'utilisation de typedef pour tout est l'odeur de code de «surgénéralité spéculative». Notez que vous déclarez des variables comme FILE * foo, jamais comme FILE foo. Pour moi, cela compte.
Bernd Jendrissek
2
@supercat: "Si les variables d'identification de fichier étaient de type FILE plutôt que FILE * ..." Mais c'est exactement l'ambiguïté que les typedefs permettent! Nous sommes juste habitués à fopen prendre un FICHIER * donc nous ne nous inquiétons pas à ce sujet, mais chaque fois que vous ajoutez typedef, vous introduisez encore un peu de surcharge cognitive: cette API veut-elle foo_t args ou foo_t *? Porter explicitement le «struct» le long améliore la localité du raisonnement, si au prix de quelques caractères supplémentaires par définition de fonction.
Bernd Jendrissek
4

Commençons par les bases et progressons.

Voici un exemple de définition de structure:

struct point
  {
    int x, y;
  };

Ici, le nom pointest facultatif.

Une Structure peut être déclarée lors de sa définition ou après.

Déclaration lors de la définition

struct point
  {
    int x, y;
  } first_point, second_point;

Déclarer après la définition

struct point
  {
    int x, y;
  };
struct point first_point, second_point;

Maintenant, notez attentivement le dernier cas ci-dessus; vous devez écrire struct pointpour déclarer des structures de ce type si vous décidez de créer ce type à un stade ultérieur de votre code.

Entrez typedef. Si vous avez l'intention de créer une nouvelle structure (la structure est un type de données personnalisé) ultérieurement dans votre programme en utilisant le même plan, l'utilisation typedeflors de sa définition peut être une bonne idée car vous pouvez économiser de la frappe à l'avenir.

typedef struct point
  {
    int x, y;
  } Points;

Points first_point, second_point;

Un mot de prudence lors de la désignation de votre type personnalisé

Rien ne vous empêche d'utiliser le suffixe _t à la fin de votre nom de type personnalisé, mais POSIX standard réserve l'utilisation du suffixe _t pour désigner les noms de type de bibliothèque standard.

Comme si
la source
3

le nom que vous donnez (facultativement) à la structure s'appelle le nom de la balise et, comme cela a été noté, n'est pas un type en soi. Pour accéder au type, le préfixe struct est requis.

GTK + à part, je ne suis pas sûr que le nom de variable soit utilisé aussi couramment qu'un typedef pour le type de structure, donc en C ++ qui est reconnu et vous pouvez omettre le mot-clé struct et utiliser le nom de variable comme nom de type également:


    struct MyStruct
    {
      int i;
    };

    // The following is legal in C++:
    MyStruct obj;
    obj.i = 7;

philsquared
la source
1

typedef ne fournira pas un ensemble de structures de données co-dépendantes. Vous ne pouvez pas faire cela avec typdef:

struct bar;
struct foo;

struct foo {
    struct bar *b;
};

struct bar {
    struct foo *f;
};

Bien sûr, vous pouvez toujours ajouter:

typedef struct foo foo_t;
typedef struct bar bar_t;

Quel est exactement l'intérêt de cela?

natersoz
la source
1

A> a typdef aide à la signification et à la documentation d'un programme en permettant la création de synonymes plus significatifs pour les types de données . De plus, ils aident à paramétrer un programme contre les problèmes de portabilité (K&R, pg147, C prog lang).

B> une structure définit un type . Structs permet un regroupement pratique d'une collection de vars pour faciliter la manipulation (K&R, pg127, C prog lang.) En une seule unité

C> typedef'ing un struct est expliqué dans A ci-dessus.

D> Pour moi, les structures sont des types ou conteneurs personnalisés ou des collections ou des espaces de noms ou des types complexes, tandis qu'un typdef n'est qu'un moyen de créer plus de surnoms.

JamesAD-0
la source
0

Il s'avère que le typedef C99 est requis. Il est obsolète, mais de nombreux outils (ala HackRank) utilisent c99 comme implémentation C pure. Et typedef y est requis.

Je ne dis pas qu'ils devraient changer (peut-être avoir deux options C) si l'exigence changeait, ceux d'entre nous qui étudient pour des entrevues sur le site seraient SOL.

Matthew Corey Brown
la source
2
"Il s'avère que C99 typedefest requis." Que voulez-vous dire?
Julien Lopez
La question concerne C, non C++. Dans les Ctypedefs, ils sont «obligatoires» (et le seront très probablement toujours). 'Obligatoire' comme dans, vous ne pourrez pas déclarer une variable Point varName;et faire en sorte que le type soit synonyme struct Point;sans a typedef struct Point Point;.
yyny
0

Dans le langage de programmation «C», le mot-clé «typedef» est utilisé pour déclarer un nouveau nom pour un objet (type struct, tableau, fonction..enum). Par exemple, je vais utiliser un «struct-s». Dans «C», nous déclarons souvent un «struct» en dehors de la fonction «principale». Par exemple:

struct complex{ int real_part, img_part }COMPLEX;

main(){

 struct KOMPLEKS number; // number type is now a struct type
 number.real_part = 3;
 number.img_part = -1;
 printf("Number: %d.%d i \n",number.real_part, number.img_part);

}

Chaque fois que je décide d'utiliser un type de struct, j'ai besoin de ce mot-clé 'struct' quelque chose '' nom '.' Typedef 'renommera simplement ce type et je peux utiliser ce nouveau nom dans mon programme chaque fois que je le souhaite. Notre code sera donc:

typedef struct complex{int real_part, img_part; }COMPLEX;
//now COMPLEX is the new name for this structure and if I want to use it without
// a keyword like in the first example 'struct complex number'.

main(){

COMPLEX number; // number is now the same type as in the first example
number.real_part = 1;
number.img)part = 5;
printf("%d %d \n", number.real_part, number.img_part);

}

Si vous avez un objet local (struct, tableau, précieux) qui sera utilisé dans l'ensemble de votre programme, vous pouvez simplement lui donner un nom en utilisant un 'typedef'.

RichardGeerify
la source
-2

Du tout, en langage C, struct / union / enum sont des macro-instructions traitées par le préprocesseur du langage C (ne vous trompez pas avec le préprocesseur qui traite "#include" et autres)

donc :

struct a
{
   int i;
};

struct b
{
   struct a;
   int i;
   int j;
};

struct b est dépensé comme quelque chose comme ceci:

struct b
{
    struct a
    {
        int i;
    };
    int i;
    int j;
}

et donc, au moment de la compilation, il évolue sur la pile comme quelque chose comme: b: int ai int i int j

c'est aussi pourquoi il est difficile d'avoir des structures auto-référentes, un préprocesseur C rond dans une boucle de déclaration qui ne peut pas se terminer.

typedef est un spécificateur de type, cela signifie que seul le compilateur C le traite et qu'il peut faire ce qu'il veut pour optimiser l'implémentation du code assembleur. Il ne dépense pas non plus stupidement un membre de type comme le fait le préprocesseur avec les structures mais utilise un algorithme de construction de référence plus complexe, donc une construction comme:

typedef struct a A; //anticipated declaration for member declaration

typedef struct a //Implemented declaration
{
    A* b; // member declaration
}A;

est autorisé et entièrement fonctionnel. Cette implémentation donne également accès à la conversion de type compilateur et supprime certains effets de bogue lorsque le thread d'exécution quitte le champ d'application des fonctions d'initialisation.

Cela signifie qu'en C, les typedefs sont plus proches de la classe C ++ que des structures solitaires.

doccpu
la source
3
Il n'est pas difficile d'avoir des structures autoréférentielles du tout. struct foo {struct foo * suivant; chose int; }
Bernd Jendrissek
4
...quoi? Dire le préprocesseur pour décrire la résolution de structset typedefs est déjà assez mauvais, mais le reste de votre écriture est tellement déroutant que j'ai du mal à en obtenir le moindre message. Une chose que je peux dire, cependant, c'est que votre idée qu'un non- typedefd structne peut pas être déclaré en avant ou utilisé comme membre opaque (pointeur) est totalement fausse. Dans votre 1er exemple, struct bpeut contenir trivialement un struct a *, non typedefrequis. Les affirmations selon lesquelles ce structne sont que des morceaux de macro-expansion stupides, et qui typedefleur donnent de nouveaux pouvoirs révolutionnaires, sont douloureusement incorrectes
underscore_d