Значения по умолчанию в структуре C - PullRequest
90 голосов
/ 15 апреля 2009

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

    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 заранее еще более утомительно, чем просто изложение ее в вызове функции. Могу ли я создать структуру где-нибудь со значениями по умолчанию, которые не заботятся, и просто установить поля, которые мне нужны, после того, как я объявил ее как локальную переменную?

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

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

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

Ответы [ 10 ]

152 голосов
/ 15 апреля 2009

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

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

15 голосов
/ 15 апреля 2009

Вы можете изменить свое секретное специальное значение на 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);
7 голосов
/ 15 апреля 2009

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

5 голосов
/ 15 апреля 2009

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

#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 голосов
/ 15 апреля 2009

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

4 голосов
/ 15 апреля 2009

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

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

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

4 голосов
/ 15 апреля 2009

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

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 ++ аналогичный шаблон имеет имя, которое я не помню сейчас.

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

2 голосов
/ 12 апреля 2017

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

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

2 голосов
/ 15 апреля 2009

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

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

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
1 голос
/ 15 апреля 2009

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

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

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

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

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