Нужен способ изменить общие поля в разных структурах - PullRequest
3 голосов
/ 18 сентября 2009

Я программирую на C здесь, для Windows и различных платформ Unix. У меня есть набор структур, которые имеют общие поля, но также поля, которые отличаются. Например:

typedef struct {
    char street[10];
    char city[10];
    char lat[10];
    char long[10];
} ADDR_A;

typedef struct {
    char street[10];
    char city[10];
    char zip[10];
    char country[10];
} ADDR_B;

Они, очевидно, не так просты, и у меня на самом деле есть 6 различных структур, но это основная идея.

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

static void updateFields( void *myStruct ) {
    strcpy( myStruct->street, streetFromSomethingElse );
    strcpy( myStruct->city, cityFromSomethingElse );
}

Ясно, что то, что я написал там, не работает из-за указателя void и какой-то тип приведен в порядок, но на самом деле не существует хорошего способа привести его (есть?) Я не против дополнительного аргумента, который как-то определяет тип структуры.

По причинам, которые не имеют отношения, Я НЕ МОГУ изменить эти структуры или объединить их или сделать что-либо еще с ними. Я должен использовать их такими, какие они есть.

Как всегда, заранее спасибо за помощь.

РЕДАКТИРОВАТЬ: Как я добавил в комментарии ниже, общие поля НЕ гарантированно будут все в начале или все в конце или что-либо еще. Они просто гарантированно существуют.

Ответы [ 11 ]

4 голосов
/ 18 сентября 2009
#define ASSIGN(type,object,member,value) do { \
    ((type *)object)->member = value; \
    } while (0)

И теперь вы можете делать такие вещи, как:

#include <stdio.h>

struct foo {
    int x;
    char y;
};

struct bar {
    char y;
    int x;
};

int main () {
    struct foo foo;
    struct bar bar;
    ASSIGN(struct foo, &foo, x, 100);
    ASSIGN(struct bar, &bar, y, 'x');
    // ...
}

Конечно, вы всегда можете расширить это, чтобы включить вызов функции для выполнения операции, поэтому поддержка memcpy или strcpy или подобного будет тривиальной.

4 голосов
/ 18 сентября 2009

Может применяться правило деметры - только раскрытие минимума структуры.

static void updateStreetAndCity ( char *street, char *city ) {
    strcpy( street, streetFromSomethingElse );
    strcpy( city, cityFromSomethingElse );
}

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

Если ваши структуры имеют разные размеры, вы можете использовать макросы для придания статического полиморфизма, как это делается в tgmath.h в C99, но это довольно трудоемкая работа.

2 голосов
/ 18 сентября 2009

Вы можете использовать макрос - если отступы, предложенные Адамом, не работают

#define UPDATE_FIELDS( myStruct, aStreet, aCity ) \
  strcpy( myStruct->street, aStreet ); \
  strcpy( myStruct->city, aCity );

но плагин c ++ лучше;)

2 голосов
/ 18 сентября 2009

Это время, когда я подключаю C ++, потому что это очевидное использование для полиморфизма. Или шаблоны. Или перегрузка функции.

Однако, если все общие поля являются первыми, вы можете просто привести указатель к ADDR_A и обработать его так:

static void updateFields( void *myStruct ) {
    ADDR_A *conv = myStruct;
    strcpy( conv->street, streetFromSomethingElse );
    strcpy( conv->city, cityFromSomethingElse );
}
2 голосов
/ 18 сентября 2009

Пока часть, которую вы хотите обновить, является общим префиксом для каждой структуры, вы можете определить новую структуру, которая перечисляет эти поля префикса, и привести ваш параметр к этому. Если предположить, что компоновка структуры префикса, которую вы создаете, идентична (то есть никаких странных проблем с заполнением компилятора), обновления будут работать на всех структурах с этим префиксом.

Редактировать: Поскольку sztomi справедливо указывает на вас, если относительные смещения полей одинаковы, то вы можете получить доступ к элементам без префикса. Тем не менее, я все еще предпочитаю создавать общую структуру, которая будет действовать как «шаблон» для этой функции; он защищает от изменений в именах полей в любой другой структуре, которую вы можете выбрать.

1 голос
/ 18 сентября 2009

Просто непроверенная идея:

struct ADDR_X_offset {
  int street;
  int city;
} ADDR_A_offset = { offsetof(ADDR_A,street), offsetof(ADDR_A,city) },
  ADDR_B_offset = { offsetof(ADDR_B,street), offsetof(ADDR_B,city) };

static void updateFields( void *myStruct, struct ADDR_X_offset *offset ) {

    strcpy( myStruct+offset->street, streetFromSomethingElse );

    strcpy( myStruct+offset->city,   cityFromSomethingElse );

}
1 голос
/ 18 сентября 2009

Один вариант, который еще не упомянут, - это создание дескрипторов для полей с использованием макроса offsetof() из <stddef.h>. Затем вы можете передать соответствующие дескрипторы функциям модификации.

typedef struct string_descriptor
{
    size_t offset;
    size_t length;
} string_descriptor;

В C99 вы можете делать такие вещи, как:

int update_string(void *structure, const string_descriptor d, const char *new_val)
{
    char *buffer = (char *)structure + d.offset;
    size_t len = strlen(new_val);
    size_t nbytes = MIN(len, d.length);
    // Optionally validate lengths, failing if the new value won't fit, etc
    memcpy(buffer, new_val, nbytes);
    buffer[nbytes] = '\0';
    return(0);  // Success
}

void some_function(void)
{
    ADDR_A a;
    ADDR_B b;
    static const string_descriptor a_des =
        { .offset = offsetof(ADDR_A, street), .length = sizeof(a.street) };
    static const string_descriptor b_des =
        { .offset = offsetof(ADDR_B, street), .length = sizeof(b.street) };

    update_string(&a, a_des, "Regent St."); // Check return status for error!
    update_string(&b, b_des, "Oxford St."); // Check return status for error!
}

Очевидно, что для одного типа это выглядит неуклюже, но при тщательном обобщении вы можете использовать несколько дескрипторов, и дескрипторы могут быть больше (более сложными), но передаваться по ссылке, и должны быть определены только один раз и т. Д. дескрипторы, та же самая функция update_string() может быть использована для обновления любого из полей в любой из двух структур. Хитрый бит - инициализатор длины sizeof(b.street); Я думаю, что для предоставления нужной информации требуется переменная правильного типа, хотя я был бы рад принять (Стандарт C99) ревизию, чтобы снять это ограничение. Не минимальные обобщенные дескрипторы могут содержать индикатор типа, имя члена и другую информацию, которая может иметь отношение к делу - я придерживался очень простой структуры, несмотря на соблазны разработки. Вы можете использовать указатели функций для предоставления разных проверок для разных строк символов - проверка для широты и долготы очень отличается от проверки для названия улицы.

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

На вас лежит ответственность за то, чтобы получить правильные дескрипторы и использовать правильные дескрипторы.


См. Также: Какова цель и тип возврата оператора __builtin_offsetof?

1 голос
/ 18 сентября 2009

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

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

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

  3. Используйте макрос, как предлагали другие . Однако я думаю, что лучше избегать макросов, когда это возможно. Это не стоит того.

Нелегко и не хорошо взломать что-то подобное в C (нет утки). Это типичный пример, когда динамический язык будет более удобным (возможно, вы могли бы вызвать python из своего кода? :))

0 голосов
/ 19 сентября 2009
typedef struct {
    char street[10];
    char city[10];
} ADDR_BASE;

typedef struct {
    struct ADDR_BASE addr;
    char lat[10];
    char long[10];
} ADDR_A;

typedef struct {
    struct ADDR_BASE addr;
    char zip[10];
    char country[10];
} ADDR_B;

таким образом вы можете передать ADDR_BASE * вашей функции.

0 голосов
/ 18 сентября 2009

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

int updateStreetAndCity(void *addr, int type);
double getLatitude(void *addr, int type);

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

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