Как скрыть объявление структуры в C?

В вопросе Почему мы должны набирать структуру так часто в C? , расслабьтесь, ответили, что:

В этом последнем случае вы не можете вернуть значение Point по значению, так как его объявление скрыто от пользователей заголовочного файла. Это метод, широко используемый в GTK +, например.

Как завершается скрытие декларации? Почему я не могу вернуть Point по значению?

ДОБАВЛЯТЬ:

Я понял, почему я не могу вернуть структуру по значению, но, по-прежнему трудно понять, почему я не могу почтить этот момент в своей функции. т.е. Если в моей структуре есть член с именем y, почему я не могу это сделать?

pointer_to_struct->y = some_value; 

Почему я должен использовать методы для этого? (Как Gtk +)

Спасибо, ребята, и извините за мой плохой английский.

Посмотрите на этот пример библиотеки, используя общеansible заголовочный файл, закрытый заголовочный файл и файл реализации.

В файле public.h :

 struct Point; Point* getSomePoint(); 

В файле private.h :

 struct Point { int x; int y; } 

В файле private.c :

 Point* getSomePoint() { /* ... */ } 

Если вы объедините эти три файла в библиотеку, вы предоставите public.h и объекту библиотеки библиотеки потребителю библиотеки.

getSomePoint должен вернуть указатель на Point , потому что public.h не определяет размер Point , только это структура и что она существует. Потребители библиотеки могут использовать указатели для Point , но не могут получить доступ к членам или скопировать их, потому что они не знают размер структуры.

Что касается вашего следующего вопроса: вы не можете разыгрывать, потому что программа, использующая библиотеку, имеет только информацию из private.h , которая не содержит деклараций участников. Поэтому он не может получить доступ к членам точечной структуры.

Вы можете видеть это как функцию инкапсуляции C, так же, как вы бы объявляли членов данных classа C ++ закрытыми.

Он имеет в виду, что вы не можете вернуть структурную ценность в заголовок, потому что для этого структура должна быть полностью объявлена. Но это происходит в файле C (объявление, которое делает X полным типом, «скрыто» в файле C и не отображается в заголовке), в его примере. Следующее объявляет только неполный тип, если это первое объявление структуры

 struct X; 

Затем вы можете объявить функцию

 struct X f(void); 

Но вы не можете определить функцию, потому что вы не можете создать переменную этого типа и тем более ее вернуть (ее размер неизвестен).

 struct X f(void) { // <- error here // ... } 

Ошибка возникает из-за того, что «x» все еще не завершен. Теперь, если вы включаете только заголовок с неполным объявлением в нем, вы не можете вызвать эту функцию, потому что выражение вызова функции приведет к неполному типу, что запрещено.

Если бы вы представили декларацию полного типа struct X между ними, это было бы действительно

 struct X; struct X f(void); // ... struct X { int data; }; struct X f(void) { // valid now: struct X is a complete type // ... } 

Это применимо и к способу использования typedef : они оба называют один и тот же (возможно, неполный) тип. Один раз, используя обычный идентификатор X , и в другое время, используя тег struct X

В файле заголовка:

 typedef struct _point * Point; 

После того, как компилятор видит это, он знает:

  • существует s-структура, называемая _point
  • существует указатель типа Point, который может ссылаться на _point

Компилятор не знает:

  • как выглядит структура _point
  • какие члены он содержит
  • насколько велика

И компилятор не только не знает об этом – мы, как программисты, тоже этого не знаем. Это означает, что мы не можем писать код, который зависит от тех свойств _point, что означает, что наш код может быть более портативным.

Учитывая приведенный выше код, вы можете писать такие функции, как:

 Point f() { .... } 

потому что Point является указателем, а указатели имеют одинаковый размер, и компилятору не нужно ничего знать о них. Но вы не можете написать функцию, которая возвращает значение:

 _point f() { .... } 

потому что компилятор ничего не знает о _point, в частности его размер, который ему нужен, чтобы построить возвращаемое значение.

Таким образом, мы можем ссылаться только на _point через тип Point, который действительно является указателем. Вот почему Standard C имеет такие типы, как FILE, к которым можно получить доступ только через указатель – вы не можете создать экземпляр структуры FILE в своем коде.

Что означает это сообщение: если вы видите заголовок

 typedef struct _Point Point; Point * point_new(int x, int y); 

то вы не знаете детали реализации Point .

Взгляните на это: непрозрачный указатель

Старый вопрос, лучший ответ:

В заголовке:

 typedef struct _Point Point; 

В файле C:

 struct _Point { int X; int Y; }; 

В качестве альтернативы использованию непрозрачных указателей (как указывали другие) вы можете вместо этого получить непрозрачный пакет байтов, если хотите избежать использования памяти кучи:

 // In public.h: struct Point { uint8_t data[SIZEOF_POINT]; // make sure this size is correct! }; void MakePoint(struct Point *p); // In private.h: struct Point { int x, y, z; }; void MakePoint(struct Point *p); // In private.c: void MakePoint(struct Point *p) { p->x = 1; p->y = 2; p->z = 3; } 

Затем вы можете создавать экземпляры структуры в стеке в клиентском коде, но клиент не знает, что в ней, – все, что он знает, это то, что это битка с байтами с заданным размером. Конечно, он все равно может получить доступ к данным, если он может угадать смещения и типы данных членов, но опять же у вас такая же проблема с непрозрачными указателями (хотя клиенты не знают размер объекта в этом случае).

Например, различные структуры, используемые в библиотеке pthreads используют структуры непрозрачных байтов для таких типов, как pthread_t , pthread_cond_t и т. Д. – вы все же можете создавать экземпляры тех, что есть в стеке (и вы обычно это делаете), но вы не представляете, что в них. Просто загляните в свой /usr/include/pthreads.h и различные файлы, которые он включает.