C дизайн стиль при использовании typedef структуры - PullRequest
0 голосов
/ 06 апреля 2011

Когда кто-либо использует структуры typedef в исходном файле, и внешняя система должна будет подключиться к этому классу через интерфейс, каков правильный способ обработки ввода?

Один из примеров, если исходный файлвнутри подсистемы есть

typedef struct
{
    double latitude;
    double longitude;
}
GPSLocation;

, а внешний класс хочет использовать следующую функцию

void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination)

Допустим, я абстрагирую это с помощью интерфейса и имею функцию, которая является внешним классомзвонки.Если функция принимает аргументы типа GPSLocation (таким образом заставляя внешнюю систему #include исходный файл с существующей структурой, а не такой внешний сейчас) или лучше сохранить все структуры typedef, используемые в подсистеме и, таким образом, имеющиеинтерфейс функции следующим образом?

void setupGPSSystem(char* navigationSystem, double startLat, double startLong, double destinationLat, double destinationLong)

Ответы [ 6 ]

2 голосов
/ 06 апреля 2011

При разработке модуля вам необходимо решить, нужен ли пользователям этого модуля прямой доступ к полям в структуре, и это определит, где вы в конечном итоге определите структуру.Я опишу несколько разных сценариев:

1.Пользователь (модуля) будет напрямую манипулировать данными GPS в вашей структуре. У вас нет выбора, кроме как определить структуру в файле заголовка (.h) как часть API.Преимущество состоит в том, что этот метод прост и дает пользователю возможность статически или динамически распределять память для вашей структуры по мере необходимости (поскольку структура структуры известна вашим другим модулям).Этот недостаток заключается в том, что этот метод не скрывает ваши данные;он может быть намеренно или случайно поврежден пользователем.

2.Пользователю не важно, что такое «местоположение GPS», он всегда будет использовать функции модуля для работы с вашей структурой данных. В этом случае ваша структура может быть непрозрачной.Вы объявляете это в файле заголовка (.h) и определяете его в файле источника (.c) (как описано в ответе Грэма).Преимущество в том, что вы можете скрыть все свои данные, что означает, что пользователь не может легко их испортить или (в случае собственной библиотеки) понять, как они были реализованы.Недостатком является то, что ваш модуль должен управлять выделением (и освобождением) вашего непрозрачного типа (расширение этой идеи см. В пункте 3 ниже).

gps_system.h

typedef struct _GPSLocation GPSLocation;

void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination);

gps_system.c

struct _GPSLocation
{
    double latitude;
    double longitude;
}

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

gps_system.h

typedef struct
{
    double latitude;
    double longitude;    
}
GPSLocation;

/*  Create a new GPS object. */
void gps_new(GPSLocation **gps);

/*  Set the GPS object's current location. */
void gps_set(GPSLocation *gps, double latitude, double longitude);

/*  Calculate the distance from a GPS coordinate to a different location. */
void gps_distance(GPSLocation *gps, double latitude, double longitude);

/*  Free all memory allocated to a gps object. */
void gps_delete(GPSLocation *gps);

gps_system.c

struct GPSLocation_private
{
    GPSLocation gps_public;

    int field_0;
    int field_1;
};

/** Convert from the public version of the gps object object to the private. */
#define gps_get_private(gps_public)  ((struct GPSLocation_private *)(((char *)(gps_public)) - offsetof(struct GPSLocation_private, gps_public)))

void gps_new(GPSLocation **gps)
{
    struct GPSLocation_private *priv;

    priv = malloc(sizeof(struct GPSLocation_private));

    if (priv)
    {
        priv->field_0 = 1234;
        priv->field_1 = 4567;
        priv->gps_public.latitude = 1111;
        priv->gps_public.longitude = 2222;
        gps = &priv->gps_public;
    }
    else
    {
        *gps = NULL;
    }    
}

void gps_set(GPSLocation *gps, double latitude, double longitude)
{
    struct GPSLocation_private *priv;

    priv = gps_get_private(gps);

    /*  Do stuff with 'priv'. */
}

void gps_delete(GPSLocation *gps)
{
    struct GPSLocation_private *priv;

    priv = gps_get_private(gps);
    free(priv);
}
2 голосов
/ 06 апреля 2011

Вы можете объявить типы в общедоступном заголовке вашего интерфейса и скрыть определения структуры в исходных файлах вашей реализации. Итак, вы можете иметь:

gps_system.h:

typedef struct GPSLocation GPSLocation;

void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination);

gps_system.c:

struct GPSLocation
{
    double latitude;
    double longitude;
}

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

1 голос
/ 06 апреля 2011

Ваша подсистема, вероятно (должна) иметь заголовочный файл, определяющий прототипы функций, которые другие люди используют для доступа к вашей системе (API).Нет ничего плохого и в том, что вы определили свою структуру в этом заголовочном файле вместе с typedef.На самом деле это очень распространено.Таким образом, если ваш прототип функции следует этому прототипу, вы в порядке.Итак, это:

#ifndef MYSUBSYSTEM_H
#define MYSUBSYSTEM_H

typedef struct
{
    double latitude;
    double longitude;
}
GPSLocation;

void setupGPSSystem(char* navigationSystem, 
                     GPSLocation *start, GPSLocation *destination);

#endif

Будет хороший заголовок.Новая строка в середине вашей функции, чтобы она соответствовала SO.#include что в коде, который ссылается на ваш, и все готово.

0 голосов
/ 06 апреля 2011

Возможно, не стоит упоминать «класс» в вопросе о структурах языка C :-) Просто, чтобы избежать путаницы, скажем, с C ++.

Вы спрашиваете "Должна ли функция принимать аргументы типа GPSLocation (таким образом, вынуждая внешнюю систему #include исходный файл с присутствующей структурой, ..."

Функция, как вы ее написали, не принимает аргументы типа GPSLocation; он принимает аргументы типа указатель на (т.е. адрес ) структуры GPSLocation, совсем другая и замечательная вещь.

И, очень правильно и правильно, вы никого не заставляете #include включать что-либо, кроме файла заголовка (.h), который определяет GPSLocation. Это потому, что в вашей записи функции (как я понимаю) вы ожидаете от указателей до экземпляров err, копий этих структур (начала и конца), существующих в вызывающей программе. Вы получите доступ к членам каждой передаваемой по ссылке структуры через их адреса / указатели.

На данный момент это лучший способ передачи структур в C - через указатели на копии этих структур вызывающей программой.

Забудьте о втором выборе, который вы дали. Навсегда!

0 голосов
/ 06 апреля 2011

Вы должны сделать пользователя #include заголовком с прототипами для функций интерфейса, так что вы можете также вставить определение структуры в этот заголовок.

0 голосов
/ 06 апреля 2011

Тип GPSLocation принадлежит API и должен находиться в заголовочном файле (файл, заканчивающийся .h), который пользователи вашего API будут #include.

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