Ответ заключается в разнице между декларацией и определением. Вы пытаетесь объявить и определить на том же шаге (в случае нового типа через typedef). Вам нужно разбить их на несколько этапов, чтобы компилятор заранее знал, о чем вы говорите.
typedef struct Person Person;
typedef struct People People;
struct Person {
char* name;
int age;
int lefthanded;
People* friends;
};
struct People {
int count;
int max;
Person* data;
};
Обратите внимание на добавление двух «пустых» typedefs вверху (объявления). Это сообщает компилятору, что новый тип Person относится к типу struct Person, поэтому, когда он видит это внутри определения struct People, он знает, что это значит.
В вашем конкретном случае вы можете обойтись только с предварительным указанием типа Люди typedef, потому что это единственный тип, используемый до его определения. К тому времени, когда вы попадаете в определение структуры People, вы уже полностью определили тип Person. Поэтому следующее также будет работать, но НЕ РЕКОМЕНДУЕТСЯ , потому что оно хрупкое:
typedef struct People People;
typedef struct {
char* name;
int age;
int lefthanded;
People* friends;
} Person;
struct People {
int count;
int max;
Person* data;
};
Если вы меняете порядок определений структуры (перемещая struct People выше typedef of Person), он снова завершится неудачей. Вот что делает его хрупким и поэтому не рекомендуется.
Обратите внимание, что этот трюк НЕ работает, если вы включаете структуру указанного типа, а не указатель на него. Так, например, следующий НЕ БУДЕТ компилировать :
typedef struct Bar Bar;
struct Foo
{
Bar bar;
};
struct Bar
{
int i;
};
Приведенный выше код выдает ошибку компилятора, потому что тип Bar является неполным, когда он пытается использовать его внутри определения struct Foo. Другими словами, он не знает, сколько места нужно выделить элементу bar структуры, потому что он не видел определения struct bar в этой точке.
Этот код скомпилирует :
typedef struct Foo Foo;
typedef struct Bar Bar;
typedef struct FooBar FooBar;
struct Foo
{
Bar *bar;
};
struct Bar
{
Foo *foo;
};
struct FooBar
{
Foo foo;
Bar bar;
FooBar *foobar;
};
Это работает, даже с круглыми указателями внутри Foo и Bar, потому что типы 'Foo' и 'Bar' были предварительно объявлены (но еще не определены), поэтому компилятор может создать указатель на них.
К тому времени, когда мы дойдем до определения FooBar, мы уже определили, насколько велики Foo и Bar, чтобы мы могли включать в них реальные объекты. Мы также можем включить самоссылочный указатель на тип FooBar, потому что мы предварительно объявили тип.
Обратите внимание, что если вы переместили определение struct FooBar выше определений struct Foo или Bar, оно не будет компилироваться по той же причине, что и в предыдущем примере (неполный тип).