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

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

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

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

  update(42, dont_care, dont_care, new_route);

Это действительно долго, и если я добавлю что-то в структуру, я должен добавить 'dont_care' для КАЖДОГО вызова для обновления (...).

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

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

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

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

Ответ 1

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

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 вы устанавливаете явно (безопасно), игнорируя те, которые вы не задали.

Ответ 2

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

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

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

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

#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);

Ответ 3

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

Ответ 4

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

#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-ism, который применяется к GCC; это хороший (безопасный) способ держать линии вместе как блок. Если впоследствии вам нужно добавить больше к макросам, например проверка проверки диапазона, вам не придется беспокоиться о нарушении таких вещей, как if/else и т.д.

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

Ответ 5

Как насчет чего-то типа:

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);

В С++ аналогичный шаблон имеет имя, которое я не помню только сейчас.

EDIT: он назвал Именованный идентификатор параметров.

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

Инициализатор типа:

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

Ответ 9

Вы можете решить проблему с помощью 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 answer, с тем преимуществом, что здесь обслуживание проще, т.е. изменение количества членов структуры автоматически обновит foo_DONT_CARE. Обратите внимание, что последняя "ложная" запятая допустима.

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

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

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

Ответ 10

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

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

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

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

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 является допустимым значением для полей в структуре.