definizione di struttura autoreferenziale?


134

Non scrivo C da molto tempo, e quindi non sono sicuro di come dovrei fare questo tipo di cose ricorsive ... Vorrei che ogni cella contenesse un'altra cella, ma ho un errore lungo il le righe di "campo" figlio "hanno tipo incompleto". Che cosa succede?

typedef struct Cell {
  int isParent;
  Cell child;
} Cell;

9
PS In realtà dattiloscrive "struct Cell" a "Cell" (che è un modello comune)
David Z,

sta probabilmente usando un compilatore C ++. dovrebbe anche usare _Bool se è davvero C.
nabiy,

Dovrebbe usare int se è davvero C :-)
paxdiablo,

2
Perché? C99 has bool - devi solo includere <stdbool.h>
Jonathan Leffler,

Risposte:


184

Chiaramente una cella non può contenere un'altra cella in quanto diventa una ricorsione infinita.

Tuttavia, una cella può contenere un puntatore a un'altra cella.

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

7
@ cs01 No, Cellnon è ancora nell'ambito.
Fredoverflow,

1
Si avrebbe un senso. Python lo consente e persino consente la serializzazione di un tale oggetto. Perché non C ++?
noɥʇʎԀʎzɐɹƆ

1
Ricevo avvisi quando provo ad assegnare Cell*a cell->child.
Tomáš Zato - Ripristina Monica il

3
@ noɥʇʎԀʎzɐɹƆ Perché Python estrae i puntatori in modo da non notarli. Dato che structs in C in pratica memorizzano semplicemente tutti i loro valori uno accanto all'altro, sarebbe impossibile effettivamente memorizzare una struttura in sé (perché quella struttura dovrebbe contenere un'altra e così via, portando a una struttura di memoria di dimensioni infinite) .
jazzpi,

1
Per una spiegazione dell'uso di struct Cell, vedere questa risposta .
wizzwizz4,

26

In C, non puoi fare riferimento al typedef che stai creando all'interno della struttura stessa. Devi usare il nome della struttura, come nel seguente programma di test:

#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;
}

Anche se è probabilmente molto più complicato di questo nello standard, puoi pensarlo come il compilatore che conosce struct Cellla prima riga typedefma che non conosce tCellfino all'ultima riga :-) È così che ricordo quella regola.


che dire di c ++ puoi per favore collegare le risposte su c ++
rimalonfire,

@rimiro, la domanda era di tipo C. Se si desidera che la risposta per un C ++ variante, si dovrebbe chiedere come una domanda.
paxdiablo,

16

Dal punto di vista teorico, le lingue possono supportare solo strutture autoreferenziali e non strutture auto inclusive.


Dal punto di vista pratico, quanto sarebbe grande un'istanza di "struct Cell"?
Marsh Ray,

26
Sulla maggior parte delle macchine, quattro byte più grandi di se stesso.
TonyK,

13

C'è un modo per aggirare questo:

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

struct Cell;
typedef struct Cell Cell;

Se lo dichiari in questo modo, dice correttamente al compilatore che struct Cell e plain-cell sono uguali. Quindi puoi usare Cell come al solito. Devo comunque usare struct Cell all'interno della stessa dichiarazione iniziale.


9
perché hai scritto di struct Cell;nuovo?
MAKZ,

@MAKZ perché il typedef non è stato eseguito dal compilatore nel momento in cui sta compilando la definizione di struct Cell.
Tyler Crompton,

2
@TylerCrompton se il blocco di codice sopra è inserito in un singolo file sorgente C, il typedef è stato "eseguito dal compilatore", rendendo il struct Cell;ridondante extra . Se, tuttavia, per qualche motivo si mettono le ultime due righe in un file di intestazione, che è includere prima di definire la Cellstruct con le prime quattro righe, poi l'extra struct Cell;è nececairy.
yyny

Questo non si compila nemmeno secondo lo standard C99.
Tomáš Zato - Ripristina Monica il

2
@YoYoYonnY No, puoi ancora solo scrivere typedef struct Cell Cell;e farà Cellun alias per struct Cell. Non importa se il compilatore ha già visto struct Cell { .... }prima.
melpomene,

8

So che questo post è vecchio, tuttavia, per ottenere l'effetto che stai cercando, potresti provare quanto segue:

#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;

In uno dei due casi menzionati nel frammento di codice sopra, DEVI dichiarare la struttura della tua cella figlio come puntatore. In caso contrario, verrà visualizzato l'errore "campo 'figlio" con tipo incompleto ". Il motivo è che "struct Cell" deve essere definito affinché il compilatore sappia quanto spazio allocare quando viene utilizzato.

Se si tenta di utilizzare "Struct Cell" all'interno della definizione di "Struct Cell", il compilatore non può ancora sapere quanto spazio "cell Cell" dovrebbe occupare. Tuttavia, il compilatore sa già quanto spazio occupa un puntatore e (con la dichiarazione in avanti) sa che "Cell" è un tipo di "struct Cell" (anche se non sa ancora quanto sia grande una "struct Cell" ). Quindi, il compilatore può definire una "cella *" all'interno della struttura che viene definita.


3

Passiamo attraverso la definizione di base di typedef. typedef usa per definire un alias per un tipo di dati esistente sia esso definito dall'utente o integrato.

typedef <data_type> <alias>;

per esempio

typedef int scores;

scores team1 = 99;

La confusione qui è con la struttura autoreferenziale, a causa di un membro dello stesso tipo di dati che non è stato definito in precedenza. Quindi in modo standard puoi scrivere il tuo codice come: -

//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;

Ma l'ultima opzione aumenta alcune righe e parole extra che di solito non vogliamo fare (siamo così pigri che sai;)). Quindi preferisci Visualizza 2.


La tua spiegazione della typedefsintassi non è corretta (considera ad esempio typedef int (*foo)(void);). I tuoi esempi View 1 e View 2 non funzionano: creano struct Cellun tipo incompleto, quindi non puoi effettivamente utilizzarlo childnel tuo codice.
melpomene,

3

Un altro metodo conveniente è quello di pre-scrivere la struttura con, tag struttura come:

//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;

1

Una struttura che contiene un riferimento a se stessa. Un evento comune di questo in una struttura che descrive un nodo per un elenco di collegamenti. Ogni nodo ha bisogno di un riferimento al nodo successivo nella catena.

struct node
{
       int data;
       struct node *next; // <-self reference
};

1

Tutte le risposte precedenti sono fantastiche, ho solo pensato di dare un'idea del perché una struttura non può contenere un'istanza del suo tipo (non un riferimento).

è molto importante notare che le strutture sono tipi di "valore", ovvero contengono il valore effettivo, quindi quando si dichiara una struttura il compilatore deve decidere la quantità di memoria da allocare a un'istanza di essa, quindi passa attraverso tutti i suoi membri e aggiunge la loro memoria per capire la memoria complessiva della struttura, ma se il compilatore ha trovato un'istanza della stessa struttura all'interno, allora questo è un paradosso (cioè per sapere quanta memoria A impiega devi decidere quanta memoria la struttura A prende!).

Ma i tipi di riferimento sono diversi, se una struttura 'A' contiene un 'riferimento' a un'istanza del proprio tipo, anche se non sappiamo ancora quanta memoria è allocata ad essa, sappiamo quanta memoria è allocata in una memoria indirizzo (cioè il riferimento).

HTH

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.