Значения по умолчанию в C Struct


93

У меня есть такая структура данных:

    struct foo {
        int id;
        int route;
        int backup_route;
        int current_route;
    }

и функция с именем update (), которая используется для запроса изменений в нем.

  обновление (42, dont_care, dont_care, new_route);

это действительно долго, и если я добавлю что-то в структуру, мне придется добавить 'dont_care' к КАЖДОМУ вызову update (...).

Я думаю о том, чтобы передать ему структуру, но заранее заполнить структуру dont_care еще более утомительно, чем просто прописать это в вызове функции. Могу ли я создать структуру где-нибудь со значениями по умолчанию dont care и просто установить поля, которые мне нужны, после того, как я объявлю ее как локальную переменную?

    struct foo bar = {.id = 42, .current_route = new_route};
    обновить (& бар);

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

и я хочу, чтобы все остальное по умолчанию было -1 (секретный код для «не волнует»)

Ответы:


156

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

const struct foo FOO_DONT_CARE = { // or maybe FOO_DEFAULT or something
    dont_care, dont_care, dont_care, dont_care
};
...
struct foo bar = FOO_DONT_CARE;
bar.id = 42;
bar.current_route = new_route;
update(&bar);

Этот код практически не требует умственных затрат на понимание косвенного обращения, и очень ясно, какие поля barвы устанавливаете явно, в то время как (безопасно) игнорируя те, которые вы не устанавливали.


6
Еще одним преимуществом этого подхода является то, что он не зависит от функций C99.
Д.Шоули,

24
Когда я перешел на этот, из проекта «выпало» 500 строк. Хотел бы я проголосовать дважды за это!
Артур Ульфельдт,

4
PTHREAD_MUTEX_INITIALIZERтакже использует это.
yingted 02

Я добавил ответ ниже, полагаясь на X-Macros для гибкости в отношении количества элементов, присутствующих в структуре.
Антонио

15

Вы можете изменить свое секретное специальное значение на 0 и использовать семантику члена структуры C по умолчанию

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

затем передаст 0 как члены bar, не указанные в инициализаторе.

Или вы можете создать макрос, который будет выполнять инициализацию по умолчанию за вас:

#define FOO_INIT(...) { .id = -1, .current_route = -1, .quux = -1, ## __VA_ARGS__ }

struct foo bar = FOO_INIT( .id = 42, .current_route = new_route );
update(&bar);

FOO_INIT работает? Я думаю, что компилятор должен пожаловаться, если вы дважды инициализируете один и тот же член.
Джонатан Леффлер,

1
Я пробовал это с помощью gcc, и он не жаловался. Кроме того, я не нашел ничего против этого в стандарте, на самом деле, есть один пример, в котором конкретно упоминается перезапись.
jpalecek

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: Инициализация должна происходить в порядке списка инициализаторов, каждый инициализатор, предусмотренный для конкретного подобъекта, отменяет любой ранее указанный инициализатор для того же самого подобъект;
altendky

2
Если это так, не могли бы вы определить DEFAULT_FOO, в котором есть все инициализаторы, а затем сделать struct foo bar = {DEFAULT_FOO, .id = 10}; ?
Джон

7

<stdarg.h>позволяет вам определять функции с переменным числом аргументов (которые принимают неопределенное количество аргументов, например printf()). Я бы определил функцию, которая принимает произвольное количество пар аргументов, одна из которых определяет свойство, которое нужно обновить, а другая - значение. Используйте enumстроку или, чтобы указать имя свойства.


Привет @Matt, как сопоставить enumпеременные с structполем? Это жестко закодировать? Тогда эта функция будет применима только к конкретным struct.
misssprite

5

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

#define UPDATE_ID(instance, id)  ({ (instance)->id= (id); })
#define UPDATE_ROUTE(instance, route)  ({ (instance)->route = (route); })
#define UPDATE_BACKUP_ROUTE(instance, route)  ({ (instance)->backup_route = (route); })
#define UPDATE_CURRENT_ROUTE(instance, route)  ({ (instance)->current_route = (route); })

Если ваш экземпляр (struct foo) является глобальным, то, конечно, вам не нужен этот параметр. Но я предполагаю, что у вас, вероятно, больше одного экземпляра. Использование блока ({...}) - это GNU-изм, который применим к GCC; это хороший (безопасный) способ сохранить строки вместе как блок. Если позже вам потребуется добавить в макросы что-то еще, например, проверку диапазона, вам не придется беспокоиться о нарушении таких функций, как операторы if / else и т. Д.

Я бы так поступил, исходя из указанных вами требований. Подобные ситуации - одна из причин, по которой я начал много использовать Python; обработка параметров по умолчанию, и это становится намного проще, чем когда-либо с C. (я предполагаю, что это плагин python, извините ;-)


4

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

update (ID, 1,
        BACKUP_ROUTE, 4,
        -1); /* -1 terminates the parameter list */

Написать функцию varargs просто - см. Http://www.eskimo.com/~scs/cclass/int/sx11b.html . Просто сопоставьте пары ключ -> значение и установите соответствующие атрибуты структуры.


4

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

например


#define set_current_route(id, route) update(id, dont_care, dont_care, route)
#define set_route(id, route) update(id, dont_care, route, dont_care)
#define set_backup_route(id, route) update(id, route, dont_care, dont_care)

Или еще лучше написать функцию для каждого случая изменения. Как вы уже заметили, вы не меняете все свойства одновременно, поэтому дайте возможность изменять только одно свойство за раз. Это не только улучшает читаемость, но также помогает справляться с различными случаями, например, вам не нужно проверять все "dont_care", потому что вы знаете, что изменяется только текущий маршрут.


в некоторых случаях меняют 3 из них за один вызов.
Артур Ульфельдт

У вас может быть такая последовательность: set_current_rout (id, route); set_route (идентификатор, маршрут); apply_change (идентификатор); или похожие. Я думаю, что у вас может быть один вызов функции для каждого сеттера. И сделайте настоящий расчет (или что-нибудь еще) позже.
quinmars

4

Как насчет чего-то вроде:

struct foo bar;
update(init_id(42, init_dont_care(&bar)));

с участием:

struct foo* init_dont_care(struct foo* bar) {
  bar->id = dont_care;
  bar->route = dont_care;
  bar->backup_route = dont_care;
  bar->current_route = dont_care;
  return bar;
}

а также:

struct foo* init_id(int id, struct foo* bar) {
  bar->id = id;
  return bar;
}

и соответственно:

struct foo* init_route(int route, struct foo* bar);
struct foo* init_backup_route(int backup_route, struct foo* bar);
struct foo* init_current_route(int current_route, struct foo* bar);

В C ++ подобный шаблон имеет название, которое я сейчас не помню.

РЕДАКТИРОВАТЬ : это называется идиомой именованных параметров .


Я считаю, что это называется конструктором.
Неизвестный,

Нет, я имел в виду то, что вы можете сделать: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen

Похоже, это называется « цепная
Reunanen

2

У меня проблемы со структурами, поэтому мне, вероятно, не хватает нескольких ключевых слов. Но почему бы не начать с глобальной структуры с инициализированными значениями по умолчанию, скопировать ее в локальную переменную, а затем изменить?

Инициализатор вроде:

void init_struct( structType * s )
{
   memcopy(s,&defaultValues,sizeof(structType));
}

Затем, когда вы захотите его использовать:

structType foo;
init_struct( &foo ); // get defaults
foo.fieldICareAbout = 1; // modify fields
update( &foo ); // pass to function

2

Вы можете решить проблему с помощью X-Macro

Вы бы изменили определение своей структуры на:

#define LIST_OF_foo_MEMBERS \
    X(int,id) \
    X(int,route) \
    X(int,backup_route) \
    X(int,current_route)


#define X(type,name) type name;
struct foo {
    LIST_OF_foo_MEMBERS 
};
#undef X

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

#define X(type,name) in->name = dont_care;    
void setFooToDontCare(struct foo* in) {
    LIST_OF_foo_MEMBERS 
}
#undef X

После обсуждения здесь можно также определить значение по умолчанию таким образом:

#define X(name) dont_care,
const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo };
#undef X

Что означает:

const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,};

И используйте его, как в ответе hlovdal , с тем преимуществом, что здесь обслуживание проще, т.е. изменение количества членов структуры будет автоматически обновлятьсяfoo_DONT_CARE . Обратите внимание, что последняя «ложная» запятая допустима .

Я впервые познакомился с концепцией X-Macros, когда мне пришлось решать эту проблему. .

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

Если у вас все в порядке со intструктурой all , вы можете опустить тип данных из LIST_OF_foo_MEMBERSи просто изменить функцию X определения структуры на#define X(name) int name;


Этот метод использует то, что препроцессор C использует позднее связывание, и это может быть очень полезно, когда вы хотите, чтобы в одном месте что-то определялось (например, состояния), а затем вы были на 100% уверены, что везде, когда используемые элементы всегда находятся в одном порядке, новые элементы добавляются автоматически, а удаленные элементы удаляются. Я не очень люблю использовать короткие имена, такие как Xи обычно добавляемые _ENTRYк имени списка, например #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal

1

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

struct foo* bar = get_foo_ptr();
foo_ref.id = 42;
foo_ref.current_route = new_route;

Или вы можете, как предложил Пукку, создать отдельные функции доступа для каждого поля структуры.

В противном случае лучшим решением, которое я могу придумать, является обработка значения «0» в поле структуры как флага «не обновлять» - поэтому вы просто создаете функцию для возврата обнуленной структуры, а затем используете ее для обновления.

struct foo empty_foo(void)
{
    struct foo bar;
    bzero(&bar, sizeof (struct bar));
    return bar;    
}

struct foo bar = empty_foo();
bar.id=42;
bar.current_route = new_route;
update(&bar);

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


Сеть - это веская причина =) Но если -1 - это значение «безразлично», тогда просто восстановите функцию empty_foo () и используйте этот подход?
gnud

извините, я по ошибке нажал отрицательный голос и заметил это позже !!! Я не могу найти способ отменить это сейчас, если не будет редактирования ...
Сиро Сантилли 郝海东 冠状 病 六四 事件 事件
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.