definição de estrutura auto-referencial?

134

Não escrevo C há muito tempo e, portanto, não tenho certeza de como devo fazer esse tipo de coisa recursiva ... Gostaria que cada célula contivesse outra célula, mas recebo um erro ao longo do tempo. linhas de "campo 'filho' tem tipo incompleto". E aí?

typedef struct Cell {
  int isParent;
  Cell child;
} Cell;
Ziggy
fonte
9
PS Na verdade, typedefs "Cell struct" para "Cell" (que é um padrão comum)
David Z
ele provavelmente está usando um compilador C ++. ele também deve estar usando _Bool se é realmente C.
nabiy
Ele deveria estar usando int se for realmente C :-) #
384
2
Por quê? C99 tem bool - você só precisa incluir <stdbool.h>
Jonathan Leffler
1
possível duplicata de C: ponteiro para struct na definição struct
Jonathan Leffler

Respostas:

184

Claramente, uma célula não pode conter outra célula, pois se torna uma recursão sem fim.

No entanto, uma célula pode conter um ponteiro para outra célula.

typedef struct Cell {
  bool isParent;
  struct Cell* child;
} Cell;
Andrew Grant
fonte
7
@ cs01 Não, Cellainda não está no escopo.
Fredoverflow
1
Isso faria sentido. O Python permite e até permite a serialização de um objeto desse tipo. Por que não C ++?
noɥʇʎԀʎzɐɹƆ
1
Recebo avisos quando tento atribuir Cell*a cell->child.
Tomáš Zato - Restabelece Monica
3
@ noɥʇʎԀʎzɐɹƆ Porque o Python abstrai os ponteiros para que você não os observe. Como structs em C basicamente apenas armazena todos os seus valores próximos um do outro, seria impossível armazenar uma estrutura em si mesma (porque essa estrutura precisaria conter outra e assim por diante, levando a uma estrutura de memória de tamanho infinito) .
jazzpi
1
Para uma explicação sobre o uso de struct Cell, consulte esta resposta .
Wizzwizz4 27/05
26

Em C, você não pode fazer referência ao typedef que está criando dentro da própria estrutura. Você precisa usar o nome da estrutura, como no seguinte programa de teste:

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

Embora seja provavelmente muito mais complicado do que isso no padrão, você pode pensar nisso como o compilador que conhece struct Cella primeira linha da, typedefmas não a conhece tCellaté a última linha :-) É assim que me lembro dessa regra.

paxdiablo
fonte
que sobre c ++ você pode por favor ligar as respostas sobre c ++
rimalonfire
@rimiro, a pergunta era C. Se você deseja a resposta para uma variante C ++, faça -a como uma pergunta.
22418
16

Do ponto de vista teórico, os idiomas podem suportar apenas estruturas auto-referenciais e não auto-inclusivas.

Sundar
fonte
Do ponto de vista prático, quão grande seria essa instância de 'struct Cell'?
Marsh Ray
26
Na maioria das máquinas, quatro bytes maiores que ele próprio.
TonyK
13

Existe uma maneira de contornar isso:

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

struct Cell;
typedef struct Cell Cell;

Se você o declarar assim, informa corretamente ao compilador que struct Cell e plain-ol'-cell são iguais. Então você pode usar o Cell como normalmente. Ainda temos que usar struct Cell dentro da própria declaração inicial.

Benjamin Horstman
fonte
9
por que você escreveu de struct Cell;novo?
MAKZ
@ MAKZ porque o typedef não foi executado pelo compilador no momento em que está compilando a definição de struct Cell.
Tyler Crompton
2
@TylerCrompton se o bloco de código acima é colocado em um único arquivo de origem C, então o typedef tem sido "executado pelo compilador", tornando o adicional struct Cell;redundante. Se, no entanto, por algum motivo você colocar as duas últimas linhas em um arquivo de cabeçalho que você incluir antes de definir a Cellestrutura com as primeiras quatro linhas, em seguida, o adicional struct Cell;é nececairy.
yyny 7/03/16
Isso nem é compilado no padrão C99.
Tomáš Zato - Restabelece Monica
2
@YoYoYonnY Não, você ainda pode escrever typedef struct Cell Cell;e ele cria Cellum apelido para struct Cell. Não importa se o compilador já viu struct Cell { .... }antes.
Melpomene
8

Sei que este post é antigo, no entanto, para obter o efeito que você está procurando, tente o seguinte:

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

Em qualquer um dos dois casos mencionados no fragmento de código acima, você DEVE declarar a estrutura da célula filho como ponteiro. Caso contrário, você receberá o erro "campo 'filho' tem tipo incompleto". O motivo é que "struct Cell" deve ser definido para que o compilador saiba quanto espaço alocar quando é usado.

Se você tentar usar "struct Cell" dentro da definição de "struct Cell", o compilador ainda não poderá saber quanto espaço "struct Cell" deve ocupar. No entanto, o compilador já sabe quanto espaço um ponteiro ocupa e (com a declaração forward) sabe que "Cell" é um tipo de "struct Cell" (embora ainda não saiba o tamanho de uma "struct Cell") ) Portanto, o compilador pode definir uma "célula *" dentro da estrutura que está sendo definida.

Shawn
fonte
3

Vamos passar pela definição básica de typedef. O typedef usa para definir um alias para um tipo de dados existente, seja ele definido pelo usuário ou embutido.

typedef <data_type> <alias>;

por exemplo

typedef int scores;

scores team1 = 99;

A confusão aqui é com a estrutura auto-referencial, devido a um membro do mesmo tipo de dados que não é definido anteriormente. Então, da maneira padrão, você pode escrever seu código como: -

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

Mas a última opção aumenta algumas linhas e palavras extras com o que normalmente não queremos (somos tão preguiçosos que você sabe;)). Então, prefira a View 2.

vineetv2821993
fonte
Sua explicação da typedefsintaxe está incorreta (considere, por exemplo typedef int (*foo)(void);). Seus exemplos de exibição 1 e exibição 2 não funcionam: eles são do struct Celltipo incompleto; portanto, você não pode usá-lo childno seu código.
Melpomene
3

Outro método conveniente é pré-digitar a estrutura com, tag de estrutura como:

//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
fonte
1

Uma estrutura que contém uma referência a si mesma. Uma ocorrência comum disso em uma estrutura que descreve um nó para uma lista de links. Cada nó precisa de uma referência para o próximo nó da cadeia.

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

Todas as respostas anteriores são ótimas, apenas pensei em fornecer uma visão sobre por que uma estrutura não pode conter uma instância de seu próprio tipo (não uma referência).

é muito importante observar que as estruturas são do tipo "valor", ou seja, elas contêm o valor real; portanto, quando você declara uma estrutura, o compilador deve decidir quanta memória alocará uma instância dela, para que ela percorra todos os seus membros e inclua sua memória para descobrir toda a memória da estrutura, mas se o compilador encontrou uma instância da mesma estrutura dentro, isso é um paradoxo (ou seja, para saber quanta memória a estrutura A leva, você precisa decidir quanta memória struct A leva!).

Mas os tipos de referência são diferentes, se uma estrutura 'A' contém uma 'referência' a uma instância de seu próprio tipo, embora ainda não sabemos quanta memória é alocada para ela, sabemos quanta memória é alocada para uma memória endereço (ou seja, a referência).

HTH

m.eldehairy
fonte