definicja struktury odniesienia do samego siebie?

134

Nie pisałem C od bardzo dawna, więc nie jestem pewien, jak mam robić tego rodzaju rekurencyjne rzeczy ... Chciałbym, aby każda komórka zawierała inną komórkę, ale pojawia się błąd w wiersze „pola„ dziecko ”mają niepełny typ”. Co tam?

typedef struct Cell {
  int isParent;
  Cell child;
} Cell;
Ziggy
źródło
9
PS Właściwie to wpisuje „struct Cell” na „Cell” (to częsty wzorzec)
David Z
prawdopodobnie używa kompilatora C ++. powinien również używać _Bool, jeśli to naprawdę C.
nabiy
Powinien używać int, jeśli to naprawdę C :-)
paxdiablo
2
Czemu? C99 ma bool - wystarczy dołączyć <stdbool.h>
Jonathan Leffler
1
możliwy duplikat C: pointer to struct w definicji struktury
Jonathan Leffler

Odpowiedzi:

184

Oczywiście komórka nie może zawierać innej komórki, ponieważ staje się ona niekończącą się rekursją.

Jednak komórka MOŻE zawierać wskaźnik do innej komórki.

typedef struct Cell {
  bool isParent;
  struct Cell* child;
} Cell;
Andrew Grant
źródło
7
@ cs01 Nie, Cellnie jest jeszcze objęty zakresem.
fredoverflow
1
To miałoby sens. Python na to pozwala, a nawet umożliwia serializację takiego obiektu. Dlaczego nie C ++?
noɥʇʎԀʎzɐɹƆ
1
Otrzymuję ostrzeżenia, gdy próbuję przypisać Cell*do cell->child.
Tomáš Zato - Przywróć Monikę
3
@ noɥʇʎԀʎzɐɹƆ Ponieważ Python usuwa wskaźniki, aby ich nie zauważyć. Ponieważ structs w C w zasadzie po prostu przechowują wszystkie swoje wartości obok siebie, niemożliwe byłoby przechowywanie struktury samej w sobie (ponieważ ta struktura musiałaby zawierać inną itd., Prowadząc do struktury pamięci o nieskończonym rozmiarze) .
jazzpi
1
Aby struct Cellzapoznać się z wyjaśnieniem zastosowania , zobacz tę odpowiedź .
wizzwizz4
26

W C nie możesz odwoływać się do typedef, które tworzysz, używając samej struktury. Musisz użyć nazwy struktury, jak w następującym programie testowym:

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

Chociaż jest to prawdopodobnie o wiele bardziej skomplikowane niż to w standardzie, możesz myśleć o tym jako o kompilatorze, który wie o struct Celltym w pierwszej linii, typedefale nie wie tCellaż do ostatniej :-) Tak zapamiętałem tę regułę.

paxdiablo
źródło
a co z c ++, czy możesz połączyć odpowiedzi dotyczące C ++
rimalonfire
@rimiro, pytanie brzmiało C. Jeśli potrzebujesz odpowiedzi na wariant C ++, zadaj to jako pytanie.
paxdiablo
16

Z teoretycznego punktu widzenia języki mogą wspierać jedynie struktury odwołujące się do samych siebie, a nie struktury inkluzywne.

Sundar
źródło
Z praktycznego punktu widzenia, jak duży byłby w rzeczywistości taki przypadek „struct Cell”?
Marsh Ray
26
Na większości maszyn cztery bajty większe niż on sam.
TonyK,
13

Jest na to sposób:

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

struct Cell;
typedef struct Cell Cell;

Jeśli zadeklarujesz to w ten sposób, poprawnie powie kompilatorowi, że struct Cell i plain-ol'-cell są takie same. Możesz więc używać Cell tak jak zwykle. Nadal jednak trzeba używać struct Cell wewnątrz samej deklaracji początkowej.

Benjamin Horstman
źródło
9
dlaczego napisałeś struct Cell;ponownie?
MAKZ
@MAKZ, ponieważ typedef nie został wykonany przez kompilator w momencie kompilowania definicji struct Cell.
Tyler Crompton
2
@TylerCrompton jeśli powyższy fragment kodu należy umieścić w jednym pliku źródłowym C, po czym typedef nie została „wykonana przez kompilator”, dzięki czemu dodatkowy struct Cell;zbędny. Jeśli jednak z jakiegoś powodu umieścisz ostatnie dwie linie w pliku nagłówkowym, który umieścisz przed zdefiniowaniem Cellstruktury z pierwszymi czterema liniami, to dodatkowe struct Cell;są potrzebne.
yyny,
To nawet nie kompiluje się w standardzie C99.
Tomáš Zato - Przywróć Monikę
2
@YoYoYonnY Nie, nadal możesz po prostu napisać typedef struct Cell Cell;i utworzy Cellalias dla struct Cell. Nie ma znaczenia, czy kompilator widział już struct Cell { .... }wcześniej.
melpomene
8

Wiem, że ten post jest stary, jednak aby uzyskać efekt, którego szukasz, możesz spróbować następujących rzeczy:

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

W każdym z dwóch przypadków wymienionych w powyższym fragmencie kodu MUSISZ zadeklarować swoją podrzędną strukturę Cell jako wskaźnik. Jeśli tego nie zrobisz, otrzymasz błąd „pole 'dziecko' ma niepełny typ”. Powodem jest to, że "struct Cell" musi być zdefiniowane, aby kompilator wiedział, ile miejsca przydzielić, gdy jest używany.

Jeśli spróbujesz użyć słowa „struct Cell” w definicji „struct Cell”, kompilator nie może jeszcze wiedzieć, ile miejsca ma zajmować „struct Cell”. Jednak kompilator już wie, ile miejsca zajmuje wskaźnik i (z deklaracją forward) wie, że "Cell" jest rodzajem "struct Cell" (chociaż nie wie jeszcze jak duża jest "struct Cell" ). Zatem kompilator może zdefiniować „Cell *” w definiowanej strukturze.

Shawn
źródło
3

Przejdźmy przez podstawową definicję typedef. typedef służy do definiowania aliasu do istniejącego typu danych, który jest zdefiniowany przez użytkownika lub wbudowany.

typedef <data_type> <alias>;

na przykład

typedef int scores;

scores team1 = 99;

Zamieszanie występuje tutaj ze strukturą odwołującą się do samego siebie, z powodu elementu członkowskiego tego samego typu danych, który nie został wcześniej zdefiniowany. Więc w standardowy sposób możesz napisać swój kod jako: -

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

Ale ostatnia opcja zwiększa dodatkowe linie i słowa, których zwykle nie chcemy robić (jesteśmy tacy leniwi, wiesz;)). Więc wolę Widok 2.

vinetv2821993
źródło
Twoje wyjaśnienie typedefskładni jest nieprawidłowe (rozważ np typedef int (*foo)(void);.). Twoje przykłady View 1 i View 2 nie działają: tworzą struct Cellniekompletny typ, więc nie możesz faktycznie użyć childw swoim kodzie.
melpomene
3

Inną wygodną metodą jest wstępne zdefiniowanie struktury za pomocą tagu struktury jako:

//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
źródło
1

Struktura, która zawiera odniesienie do siebie. Jest to częste zjawisko w strukturze opisującej węzeł dla listy połączeń. Każdy węzeł potrzebuje odniesienia do następnego węzła w łańcuchu.

struct node
{
       int data;
       struct node *next; // <-self reference
};
DARSHINI DESAI
źródło
1

Wszystkie poprzednie odpowiedzi są świetne. Pomyślałem tylko, że chcę wyjaśnić, dlaczego struktura nie może zawierać instancji własnego typu (nie odniesienia).

bardzo ważne jest, aby pamiętać, że struktury są typami wartości, tj. zawierają rzeczywistą wartość, więc kiedy deklarujesz strukturę, kompilator musi zdecydować, ile pamięci przeznaczyć na jej instancję, więc przechodzi przez wszystkie jej elementy i dodaje w celu obliczenia całej pamięci struktury, ale jeśli kompilator znalazł wewnątrz instancję tej samej struktury, to jest to paradoks (tj. aby wiedzieć, ile pamięci zajmuje struktura A, musisz zdecydować, ile pamięci struct A trwa!).

Ale typy odwołań są różne, jeśli struktura „A” zawiera „odwołanie” do instancji własnego typu, chociaż nie wiemy jeszcze, ile pamięci jest do niej przydzielone, wiemy, ile pamięci jest przydzielone do pamięci adres (tj. odniesienie).

HTH

m.eldehairy
źródło