Как я могу скрыть объявление структуры в C? - PullRequest
10 голосов
/ 20 июля 2009

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

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

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

ADD:

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

 pointer_to_struct->y = some_value; 

Почему я должен использовать методы, чтобы сделать это? (Вроде Gtk +)

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

Ответы [ 7 ]

30 голосов
/ 20 июля 2009

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

В файле 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, точно так же, как вы объявили бы члены-данные класса C ++ частными.

5 голосов
/ 20 июля 2009

В заголовочном файле:

typedef struct _point * Point;

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

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

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

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

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

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

Point f() {
   ....
}

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

_point f() {
  ....
}

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

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

5 голосов
/ 20 июля 2009

Он имеет в виду, что вы не можете вернуть значение struct by-value в заголовке, потому что для этого структура должна быть полностью объявлена. Но это происходит в файле 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.

3 голосов
/ 20 июля 2009

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

3 голосов
/ 20 июля 2009

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

typedef struct _Point Point;

Point * point_new(int x, int y);

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

2 голосов
/ 26 января 2013

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

В заголовочном файле:

typedef struct _Point Point;

В файле C:

struct _Point
{
   int X;
   int Y;
};
1 голос
/ 20 июля 2009

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

// 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 и различные файлы, которые он включает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...