นิยามโครงสร้างอ้างอิงตนเอง?


137

ฉันไม่ได้เขียน C มานานมากแล้วดังนั้นฉันจึงไม่แน่ใจว่าฉันควรจะทำสิ่งที่เรียกซ้ำเหล่านี้อย่างไร ... ฉันต้องการให้แต่ละเซลล์มีเซลล์อื่น แต่ฉันได้รับข้อผิดพลาด บรรทัด "field" child "มีประเภทที่ไม่สมบูรณ์" ว่าไง?

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

9
ปล. จริงๆแล้วมันพิมพ์ "struct Cell" เป็น "Cell" (นั่นคือรูปแบบทั่วไป)
David Z

เขาอาจใช้คอมไพเลอร์ C ++ เขาควรจะใช้ _Bool ด้วยถ้าเป็น C.
nabiy

เขาควรใช้ int ถ้าเป็น C :-) จริงๆ
paxdiablo

2
ทำไม? C99 มีบูล - คุณต้องใส่ <stdbool.h>
Jonathan Leffler

คำตอบ:


189

เห็นได้ชัดว่าเซลล์ไม่สามารถมีเซลล์อื่นได้เนื่องจากจะกลายเป็นการเรียกซ้ำที่ไม่มีวันสิ้นสุด

อย่างไรก็ตามเซลล์สามารถมีตัวชี้ไปยังเซลล์อื่นได้

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

8
@ cs01 ไม่ใช่Cellยังไม่อยู่ในขอบเขต
fredoverflow

1
มันจะสมเหตุสมผล Python อนุญาตและยังอนุญาตการทำให้เป็นอนุกรมของวัตถุดังกล่าว ทำไมไม่ใช้ C ++?
noɥʇʎԀʎzɐɹƆ

1
ฉันได้รับการเตือนเมื่อฉันพยายามที่จะกำหนดไปCell* cell->child
Tomáš Zato - คืนสถานะ Monica

4
@ noɥʇʎԀʎzɐɹƆเนื่องจาก Python นามธรรมชี้ไปที่ตัวชี้เพื่อที่คุณจะได้ไม่สังเกตเห็น เนื่องจากstructโดยทั่วไปแล้ว s ใน C จะเก็บค่าทั้งหมดไว้ติดกันจึงเป็นไปไม่ได้ที่จะจัดเก็บโครงสร้างในตัวมันเอง (เพราะโครงสร้างนั้นจะต้องมีโครงสร้างอื่นไปเรื่อย ๆ ซึ่งนำไปสู่โครงสร้างหน่วยความจำที่มีขนาดไม่สิ้นสุด) .
jazzpi

1
สำหรับคำอธิบายเกี่ยวกับการใช้งานstruct Cellโปรดดูคำตอบนี้
wizzwizz4

29

ในภาษา C คุณไม่สามารถอ้างอิง typedef ที่คุณสร้างขึ้นโดยใช้โครงสร้างเอง คุณต้องใช้ชื่อโครงสร้างเช่นเดียวกับในโปรแกรมทดสอบต่อไปนี้:

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

แม้ว่ามันอาจจะซับซ้อนกว่านี้มากในมาตรฐาน แต่คุณสามารถคิดว่ามันเป็นคอมไพเลอร์ที่รู้เกี่ยวกับstruct Cellบรรทัดแรกของบรรทัดtypedefแต่ไม่รู้เรื่องtCellจนถึงบรรทัดสุดท้าย :-) นั่นคือสิ่งที่ฉันจำกฎนั้นได้


แล้ว c ++ ช่วยลิงค์คำตอบเกี่ยวกับ c ++ ได้
ไหม

@rimiro คำถามคือ C หนึ่ง หากคุณต้องการคำตอบสำหรับตัวแปร C ++ คุณควรถามเป็นคำถาม
paxdiablo

16

จากมุมมองทางทฤษฎีภาษาสามารถรองรับเฉพาะโครงสร้างอ้างอิงตัวเองเท่านั้นที่ไม่ใช่โครงสร้างแบบรวมตัวเอง


จากมุมมองในทางปฏิบัติตัวอย่างของ 'struct Cell' จะมีขนาดใหญ่เพียงใด?
Marsh Ray

27
ในเครื่องส่วนใหญ่สี่ไบต์ใหญ่กว่าตัวมันเอง
TonyK

13

มีวิธีแก้ไขดังนี้:

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

struct Cell;
typedef struct Cell Cell;

ถ้าคุณประกาศแบบนี้มันจะบอกคอมไพเลอร์อย่างถูกต้องว่า struct Cell และ plain-ol'-cell นั้นเหมือนกัน ดังนั้นคุณสามารถใช้ Cell ได้เหมือนปกติ ยังคงต้องใช้ struct Cell ภายในการประกาศครั้งแรกแม้ว่า


9
ทำไมคุณถึงเขียนstruct Cell;อีกครั้ง?
MAKZ

@MAKZ เนื่องจาก typedef ไม่ได้ถูกดำเนินการโดยคอมไพเลอร์ในเวลาที่รวบรวมคำจำกัดความของstruct Cell.
Tyler Crompton

2
@TylerCrompton หากบล็อกโค้ดด้านบนถูกใส่ลงในซอร์สไฟล์ C ไฟล์เดียว typedef จะถูก "ดำเนินการโดยคอมไพเลอร์" ทำให้ส่วนเกินstruct Cell;ซ้ำซ้อน แต่ถ้าด้วยเหตุผลบางอย่างที่คุณใส่ทั้งสองบรรทัดสุดท้ายเป็นไฟล์ส่วนหัวที่คุณรวมถึงก่อนที่คุณกำหนดCellstruct กับสี่บรรทัดแรกแล้วพิเศษstruct Cell;คือ nececairy
yyny

สิ่งนี้ไม่ได้รวบรวมภายใต้มาตรฐาน C99 ด้วยซ้ำ
Tomáš Zato - คืนสถานะ Monica

2
@YoYoYonnY ไม่มีคุณยังสามารถเพียงแค่เขียนtypedef struct Cell Cell;และมันจะทำให้นามแฝงสำหรับCell struct Cellไม่สำคัญว่าคอมไพเลอร์จะเคยเห็นstruct Cell { .... }มาก่อน
melpomene

8

ฉันรู้ว่าโพสต์นี้เก่าอย่างไรก็ตามเพื่อให้ได้เอฟเฟกต์ที่คุณต้องการคุณอาจต้องลองทำสิ่งต่อไปนี้:

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

ในสองกรณีที่กล่าวถึงในส่วนของโค้ดด้านบนคุณต้องประกาศโครงสร้างเซลล์ลูกของคุณเป็นตัวชี้ หากไม่ทำเช่นนั้นคุณจะได้รับข้อผิดพลาด "field" child "has not complete type" เหตุผลก็คือต้องมีการกำหนด "struct Cell" เพื่อให้คอมไพเลอร์ทราบว่าจะจัดสรรพื้นที่เท่าใดเมื่อถูกใช้งาน

หากคุณพยายามใช้ "struct Cell" ภายในคำจำกัดความของ "struct Cell" คอมไพเลอร์จะยังไม่สามารถทราบได้ว่า "struct Cell" ควรใช้พื้นที่เท่าใด อย่างไรก็ตามคอมไพเลอร์รู้อยู่แล้วว่าตัวชี้ใช้พื้นที่เท่าใดและ (ด้วยการประกาศไปข้างหน้า) ก็รู้ว่า "เซลล์" เป็น "โครงสร้างเซลล์" ชนิดหนึ่ง (แม้ว่าจะยังไม่รู้ว่า "เซลล์โครงสร้าง" มีขนาดใหญ่เพียงใด ). ดังนั้นคอมไพเลอร์สามารถกำหนด "Cell *" ภายในโครงสร้างที่กำลังกำหนด


4

อีกวิธีหนึ่งที่สะดวกคือการพิมพ์โครงสร้างล่วงหน้าด้วยแท็กโครงสร้างเป็น:

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

3

มาดูนิยามพื้นฐานของ typedef typedef ใช้เพื่อกำหนดนามแฝงให้กับชนิดข้อมูลที่มีอยู่ไม่ว่าจะเป็นผู้ใช้กำหนดเองหรือ inbuilt

typedef <data_type> <alias>;

ตัวอย่างเช่น

typedef int scores;

scores team1 = 99;

ความสับสนในที่นี้เกิดขึ้นกับโครงสร้างการอ้างอิงตัวเองเนื่องจากสมาชิกของประเภทข้อมูลเดียวกันซึ่งไม่ได้กำหนดไว้ก่อนหน้านี้ ดังนั้นในวิธีมาตรฐานคุณสามารถเขียนโค้ดของคุณเป็น: -

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

แต่ตัวเลือกสุดท้ายจะเพิ่มบรรทัดและคำพิเศษบางคำโดยปกติเราไม่ต้องการทำ (เราขี้เกียจคุณรู้ไหม;)) ดังนั้นชอบ View 2


คำอธิบายของคุณเกี่ยวกับtypedefไวยากรณ์ไม่ถูกต้อง (พิจารณาเช่นtypedef int (*foo)(void);) ตัวอย่าง View 1 และ View 2 ของคุณไม่ทำงาน: struct Cellเป็นประเภทที่ไม่สมบูรณ์ดังนั้นคุณจึงไม่สามารถใช้childในโค้ดของคุณได้
melpomene

1

โครงสร้างที่มีการอ้างอิงถึงตัวมันเอง เหตุการณ์นี้เกิดขึ้นทั่วไปในโครงสร้างที่อธิบายโหนดสำหรับลิงค์ลิสต์ แต่ละโหนดต้องการการอ้างอิงไปยังโหนดถัดไปในห่วงโซ่

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

1

คำตอบก่อนหน้านี้ดีมากฉันแค่คิดว่าจะให้ข้อมูลเชิงลึกว่าทำไมโครงสร้างจึงไม่มีอินสแตนซ์ประเภทของตัวเอง (ไม่ใช่ข้อมูลอ้างอิง)

สิ่งสำคัญอย่างยิ่งที่ต้องสังเกตว่าโครงสร้างเป็นประเภท 'ค่า' กล่าวคือมีค่าจริงดังนั้นเมื่อคุณประกาศโครงสร้างคอมไพเลอร์จะต้องตัดสินใจว่าจะจัดสรรหน่วยความจำให้กับอินสแตนซ์ของมันเท่าใดจึงจะส่งผ่านสมาชิกทั้งหมดและเพิ่ม เพิ่มหน่วยความจำเพื่อหาหน่วยความจำโอเวอร์ทั้งหมดของโครงสร้าง แต่ถ้าคอมไพเลอร์พบอินสแตนซ์ของโครงสร้างเดียวกันภายในสิ่งนี้จะเป็นความขัดแย้ง (กล่าวคือเพื่อให้ทราบว่าหน่วยความจำของโครงสร้าง A ใช้เวลาเท่าใดคุณต้องตัดสินใจว่าหน่วยความจำเท่าใด โครงสร้าง A ใช้เวลา!)

แต่ประเภทการอ้างอิงจะแตกต่างกันหากโครงสร้าง 'A' มี 'การอ้างอิง' ไปยังอินสแตนซ์ของประเภทของมันเองแม้ว่าเราจะยังไม่รู้ว่าหน่วยความจำถูกจัดสรรให้กับหน่วยความจำเท่าใด แต่เราก็รู้ว่าหน่วยความจำถูกจัดสรรให้กับหน่วยความจำเท่าใด ที่อยู่ (เช่นข้อมูลอ้างอิง)

HTH

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.