Стиль оформления C при использовании структур typedef

#c #design-patterns

#c #дизайн-шаблоны #шаблоны проектирования

Вопрос:

Когда структуры 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)
  

Комментарии:

1. Так что же это за ссылки на классы?

2. Мой мозг все еще находится в objective-c. 😛

Ответ №1:

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

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. ОК. Я определяю свое местоположение GPSLocation в своем файле .h. Я буду использовать несколько GSLocations, поэтому, возможно, мне не следует определять это как указано выше?

Ответ №2:

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

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 = amp;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);
}
  

Ответ №3:

Ваша подсистема, вероятно (должна) иметь файл заголовка, определяющий прототипы функций, которые другие люди используют для доступа к вашей системе (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 это в коде, который ссылается на ваш, и вы настроены.

Комментарии:

1. У меня именно это. Мне просто интересно, что другим нужно знать, из чего состоит GPSLocation, а не просто разрешать им отправлять дубли

2. То, из чего состоит GPSLocation, в любом случае общедоступно в вашем втором прототипе. Конечный пользователь должен передать эту информацию; единственная разница заключается в использовании структур или нет. Теперь, если конечному пользователю не нужно передавать типы GPSLocation (т. Е. Они получены из какой-либо другой части вашей подсистемы), то скрытие реализации может иметь смысл.

Ответ №4:

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

Комментарии:

1. @Helium3: Да, исходные файлы содержат исходный код, заголовочные файлы должны содержать только объявления.

Ответ №5:

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

Ответ №6:

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

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

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

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

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

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