définition de structure auto-référentielle?

134

Je n'écris pas en C depuis très longtemps, donc je ne suis pas sûr de la façon dont je devrais faire ce genre de choses récursives ... Je voudrais que chaque cellule contienne une autre cellule, mais j'obtiens une erreur le long du les lignes du "champ 'enfant' sont de type incomplet". Quoi de neuf?

typedef struct Cell {
  int isParent;
  Cell child;
} Cell;
Ziggy
la source
9
PS En fait, il typedefs "struct Cell" à "Cell" (c'est un modèle courant)
David Z
il utilise probablement un compilateur C ++. il devrait également utiliser _Bool si c'est vraiment C.
nabiy
Il devrait utiliser int si c'est vraiment C :-)
paxdiablo
2
Pourquoi? C99 a bool - il vous suffit d'inclure <stdbool.h>
Jonathan Leffler
1
duplication possible de C: pointeur vers struct dans la définition de struct
Jonathan Leffler

Réponses:

184

Il est clair qu'une cellule ne peut pas contenir une autre cellule car elle devient une récursivité sans fin.

Cependant, une cellule peut contenir un pointeur vers une autre cellule.

typedef struct Cell {
  bool isParent;
  struct Cell* child;
} Cell;
Andrew Grant
la source
7
@ cs01 Non, Celln'est pas encore dans le champ d'application.
fredoverflow
1
Cela aurait du sens. Python le permet et permet même la sérialisation d'un tel objet. Pourquoi pas C ++?
noɥʇʎԀʎzɐɹƆ
1
Je reçois des avertissements lorsque j'essaie de m'attribuer Cell*à cell->child.
Tomáš Zato - Réintégrer Monica
3
@ noɥʇʎԀʎzɐɹƆ Parce que Python fait abstraction des pointeurs pour que vous ne les remarquiez pas. Puisque les structs en C stockent essentiellement toutes leurs valeurs les unes à côté des autres, il serait impossible de stocker réellement une structure en elle-même (car cette structure devrait en contenir une autre et ainsi de suite, conduisant à une structure mémoire de taille infinie) .
jazzpi
1
Pour une explication de l'utilisation de struct Cell, voir cette réponse .
wizzwizz4
26

En C, vous ne pouvez pas référencer le typedef que vous créez avec la structure elle-même. Vous devez utiliser le nom de la structure, comme dans le programme de test suivant:

#include <stdio.h>
#include <stdlib.h>

typedef struct Cell {
  int cellSeq;
  struct Cell* next; /* 'tCell *next' will not work here */
} tCell;

int main(void) {
    int i;
    tCell *curr;
    tCell *first;
    tCell *last;

    /* Construct linked list, 100 down to 80. */

    first = malloc (sizeof (tCell));
    last = first;
    first->cellSeq = 100;
    first->next = NULL;
    for (i = 0; i < 20; i++) {
        curr = malloc (sizeof (tCell));
        curr->cellSeq = last->cellSeq - 1;
        curr->next = NULL;
        last->next = curr;
        last = curr;
    }

    /* Walk the list, printing sequence numbers. */

    curr = first;
    while (curr != NULL) {
        printf ("Sequence = %d\n", curr->cellSeq);
        curr = curr->next;
    }

    return 0;
}

Bien que ce soit probablement beaucoup plus compliqué que cela dans le standard, vous pouvez le considérer comme le compilateur connaissant struct Cellla première ligne du typedefmais ne le sachant pas tCellavant la dernière ligne :-) C'est comme ça que je me souviens de cette règle.

paxdiablo
la source
qu'en est-il de c ++ pouvez-vous s'il vous plaît lier les réponses concernant c ++
rimalonfire
@rimiro, la question était une question C. Si vous voulez la réponse pour une variante C ++, vous devez la poser sous forme de question.
paxdiablo
16

Du point de vue théorique, les langues ne peuvent supporter que des structures autoréférentielles et non des structures auto-inclusives.

Sundar
la source
Du point de vue pratique, quelle serait la taille réelle d'une telle instance de «struct Cell»?
Marsh Ray
26
Sur la plupart des machines, quatre octets plus grand que lui-même.
TonyK
13

Il existe une sorte de moyen de contourner cela:

struct Cell {
  bool isParent;
  struct Cell* child;
};

struct Cell;
typedef struct Cell Cell;

Si vous le déclarez comme ceci, cela indique correctement au compilateur que struct Cell et plain-ol'-cell sont identiques. Vous pouvez donc utiliser Cell comme d'habitude. Il faut encore utiliser struct Cell dans la déclaration initiale elle-même.

Benjamin Horstman
la source
9
pourquoi avez-vous réécrit struct Cell;?
MAKZ
@MAKZ car le typedef n'a pas été exécuté par le compilateur au moment où il compile la définition de struct Cell.
Tyler Crompton
2
@TylerCrompton si le bloc de code ci-dessus est placé dans un seul fichier source C, alors le typedef a été "exécuté par le compilateur", ce qui rend le plus struct Cell;redondant. Si, cependant, pour une raison quelconque, vous mettez les deux dernières lignes dans un fichier d'en-tête que vous incluez avant de définir la Cellstructure avec les quatre premières lignes, alors le supplément struct Cell;est nécessaire.
yyny
Cela ne compile même pas sous la norme C99.
Tomáš Zato - Réintégrer Monica
2
@YoYoYonnY Non, vous pouvez toujours écrire typedef struct Cell Cell;et cela créera Cellun alias pour struct Cell. Peu importe que le compilateur ait déjà vu struct Cell { .... }.
melpomene
8

Je sais que cet article est ancien, cependant, pour obtenir l'effet que vous recherchez, vous pouvez essayer ce qui suit:

#define TAKE_ADVANTAGE

/* Forward declaration of "struct Cell" as type Cell. */
typedef struct Cell Cell;

#ifdef TAKE_ADVANTAGE
/*
   Define Cell structure taking advantage of forward declaration.
*/
struct Cell
{
   int isParent;
   Cell *child;
};

#else

/*
   Or...you could define it as other posters have mentioned without taking
   advantage of the forward declaration.
*/
struct Cell
{
   int isParent;
   struct Cell *child;
};

#endif

/*
    Some code here...
*/

/* Use the Cell type. */
Cell newCell;

Dans l'un ou l'autre des deux cas mentionnés dans le fragment de code ci-dessus, vous DEVEZ déclarer votre structure Cell enfant en tant que pointeur. Si vous ne le faites pas, vous obtiendrez l'erreur "le champ" l'enfant "a un type incomplet". La raison est que "struct Cell" doit être défini pour que le compilateur sache combien d'espace allouer lorsqu'il est utilisé.

Si vous essayez d'utiliser "struct Cell" dans la définition de "struct Cell", alors le compilateur ne peut pas encore savoir combien d'espace "struct Cell" est censé prendre. Cependant, le compilateur sait déjà combien d'espace un pointeur prend, et (avec la déclaration forward) il sait que "Cell" est un type de "struct Cell" (bien qu'il ne sache pas encore la taille d'une "struct Cell" ). Ainsi, le compilateur peut définir une "Cell *" dans la structure en cours de définition.

Shawn
la source
3

Passons en revue la définition de base de typedef. typedef utilise pour définir un alias vers un type de données existant, qu'il soit défini par l'utilisateur ou intégré.

typedef <data_type> <alias>;

par exemple

typedef int scores;

scores team1 = 99;

La confusion est ici avec la structure auto-référentielle, due à un membre du même type de données qui n'est pas défini précédemment. Donc, de manière standard, vous pouvez écrire votre code comme suit: -

//View 1
typedef struct{ bool isParent; struct Cell* child;} Cell;

//View 2
typedef struct{
  bool isParent;
  struct Cell* child;
} Cell;

//Other Available ways, define stucture and create typedef
struct Cell {
  bool isParent;
  struct Cell* child;
};

typedef struct Cell Cell;

Mais la dernière option augmente quelques lignes et mots supplémentaires avec généralement nous ne voulons pas faire (nous sommes tellement paresseux vous savez;)). Préférez donc la vue 2.

vineetv2821993
la source
Votre explication de la typedefsyntaxe est incorrecte (considérez par exemple typedef int (*foo)(void);). Vos exemples View 1 et View 2 ne fonctionnent pas: ils créent struct Cellun type incomplet, vous ne pouvez donc pas les utiliser childdans votre code.
melpomene
3

Une autre méthode pratique consiste à pré-typer la structure avec la balise structure comme suit:

//declare new type 'Node', as same as struct tag
typedef struct Node Node;
//struct with structure tag 'Node'
struct Node
{
int data;
//pointer to structure with custom type as same as struct tag
Node *nextNode;
};
//another pointer of custom type 'Node', same as struct tag
Node *node;
Keynes
la source
1

Une structure qui contient une référence à elle-même. Une occurrence commune de ceci dans une structure qui décrit un nœud pour une liste de liens. Chaque nœud a besoin d'une référence au nœud suivant de la chaîne.

struct node
{
       int data;
       struct node *next; // <-self reference
};
DARSHINI DESAI
la source
1

Toutes les réponses précédentes sont excellentes, j'ai juste pensé à donner un aperçu de la raison pour laquelle une structure ne peut pas contenir une instance de son propre type (pas une référence).

il est très important de noter que les structures sont des types «valeur», c'est-à-dire qu'elles contiennent la valeur réelle, donc lorsque vous déclarez une structure, le compilateur doit décider de la quantité de mémoire à allouer à une instance de celle-ci, il passe donc par tous ses membres et ajoute dans leur mémoire pour comprendre la mémoire totale de la structure, mais si le compilateur a trouvé une instance de la même structure à l'intérieur, alors c'est un paradoxe (c'est-à-dire pour savoir combien de mémoire la structure A prend, vous devez décider de la quantité de mémoire struct A prend!).

Mais les types de référence sont différents, si une structure 'A' contient une 'référence' à une instance de son propre type, bien que nous ne sachions pas encore combien de mémoire lui est allouée, nous savons combien de mémoire est allouée à une mémoire adresse (c'est-à-dire la référence).

HTH

m.eldehairy
la source