Значення за замовчуванням у структурі C.


92

У мене така структура даних:

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

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

  оновити (42, dont_care, dont_care, new_route);

це дійсно довго, і якщо я додаю щось до структури, я повинен додати 'dont_care' до КОЖНОГО виклику для оновлення (...).

Я думаю замість того, щоб передати йому структуру, але заповнення структури за допомогою 'dont_care' заздалегідь є навіть більш нудним, ніж просто написання її у виклику функції. Чи можу я створити структуру десь із значеннями за замовчуванням не дбати і просто встановити поля, про які я дбаю, після того, як я оголошував це як локальну змінну?

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

Який найелегантніший спосіб передати лише інформацію, яку я хочу висловити, у функцію оновлення?

і я хочу, щоб усе інше за замовчуванням становило -1 (секретний код для "не дбати")

Відповіді:


155

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

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

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

15

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

Чи працює FOO_INIT? Думаю, компілятор повинен скаржитися, якщо ви двічі ініціалізуєте одного і того ж члена.
Джонатан Леффлер

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

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: Ініціалізація повинна відбуватися в списку Initializer порядку, кожен ініціалізатор передбачений для конкретного підоб'єкту відкидаючи будь раніше перерахований ініціалізатор для того ж суб'єкт;
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.


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 (id, route); apply_change (id); або подібні. Я думаю, що ви можете мати один виклик функції для кожного сетера. І зробіть реальний розрахунок (або що завгодно) пізніше.
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));
Реунанен,

Це , здається, називається «ланцюгом ініціалізації Smalltalk-стиль», принаймні тут: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal / ...
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структурою, ви можете опустити тип даних 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

вибачте, я помилково натиснув "за" і лише пізніше помітив !!! Я не можу знайти жодного способу скасувати це зараз, якщо не буде редагування ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.