Як ініціалізувати структуру відповідно до стандартів мови програмування на С


465

Я хочу ініціалізувати елемент структури, розділити на декларування та ініціалізацію. Ось що я маю:

typedef struct MY_TYPE {
  bool flag;
  short int value;
  double stuff;
} MY_TYPE;

void function(void) {
  MY_TYPE a;
  ...
  a = { true, 15, 0.123 }
}

Чи це спосіб оголосити та ініціалізувати локальну змінну MY_TYPEвідповідно до стандартів мови програмування C (C89, C90, C99, C11 тощо)? Або щось краще чи принаймні працює?

Оновлення У мене з’явився елемент статичної ініціалізації, де я встановлював кожну підсистему відповідно до моїх потреб.


3
ви дійсно повинні прийняти кращу відповідь, я бачу, що вам довелося скористатися поганим посібником з кодування, але ви все одно не повинні підказувати іншим людям, що це правильний спосіб зробити це.
Каролі Горват

@KarolyHorvath добре, більшість хороших відповідей характерні саме для C99. Можливо, моє запитання є дублікатом stackoverflow.com/questions/6624975/… ?
плакати

якби це був ваш початковий намір, то, ймовірно, так, але тоді 1) голоси були б дуже оманливими. 2) з найпопулярніших результатів пошуку це єдиний, який показує шлях C99 ..... було б краще використовувати цю сторінку повторно для демонстрації C99 ... (мабуть, люди почали посилати цю сторінку, щоб показати, як це зробити зроби це)
Каролі Хорват

10
Цікаво, що прийнята (і сильно підкріплена) відповідь насправді не дає відповіді на питання, навіть як це було зроблено спочатку. Позначені ініціалізатори не вирішують проблеми ОП, яка полягає в розділенні декларації від ініціалізації. Для до 1999 року С єдиним реальним рішенням є призначення кожного члена; для C99 і пізніше, складне буквальне, як у відповіді CesarB, є рішенням. (Звичайно, власне ініціалізатор, із позначеннями або без них, був би ще кращим, але очевидно, що ОП осідає справді поганим стандартом кодування.)
Кіт Томпсон,

31
Строго кажучи, термін "ANSI C" зараз відноситься до стандарту ISO 2011, який прийняв ANSI. Але на практиці термін "ANSI C" зазвичай посилається на (офіційно застарілий) стандарт 1989 року. Наприклад, "gcc -ansi" все ще застосовує стандарт 1989 року. Оскільки ISO опублікував стандарти 1990, 1999 та 2011 років, краще уникати терміна "ANSI C" та посилатися на дату стандарту, якщо є можливість плутати.
Кіт Томпсон

Відповіді:


728

У (ANSI) C99 ви можете використовувати призначений ініціалізатор для ініціалізації структури:

MY_TYPE a = { .flag = true, .value = 123, .stuff = 0.456 };

Редагувати: інші члени ініціалізуються як нульові: "Опущені члени поля неявно ініціалізуються так само, як об'єкти, які мають статичну тривалість зберігання." ( https://gcc.gnu.org/onlinedocs/gcc/Designized-Inits.html )


87
приголомшливий, спасибі ви навіть можете вкладати em: статичний oxeRay2f ray = {.unitDir = {.x = 1.0f, .y = 0.0f}};
orion elenzil

4
Це зовсім не відповідає на питання. OP хоче відокремити ініціалізацію від декларації.
osvein

3
C99! = ANSI C - Отже, це не працює ні в C89, ні в C90.
Тім Вісманн

3
C99 - ANSI C, дивіться це
Cutberto Ocampo

3
@CutbertoOcampo Я розумію, що сталося зараз. Первісне запитання просило рішення в ANSI C, очевидно, він хотів відповіді лише для C89. У 2016 році питання було відредаговано та "ANSI C" було видалено, що тепер важко зрозуміти, чому ця відповідь та коментарі згадують "(ANSI) C99".
Етьєн

198

Ви можете зробити це зі складеним буквалом . Відповідно до цієї сторінки, вона працює в C99 (що також вважається ANSI C ).

MY_TYPE a;

a = (MY_TYPE) { .flag = true, .value = 123, .stuff = 0.456 };
...
a = (MY_TYPE) { .value = 234, .stuff = 1.234, .flag = false };

Позначення в ініціалізаторах необов’язкові; Ви також можете написати:

a = (MY_TYPE) { true,  123, 0.456 };
...
a = (MY_TYPE) { false, 234, 1.234 };

Отже, призначені ініціалізатори призначені для того, коли ви заявляєте та визначаєте одночасно, але складні літерали - це коли ви визначаєте вже оголошену змінну?
Геремія

@Geremia вам потрібно спочатку визначити структуру в обох випадках, але за допомогою призначених ініціалізаторів ви зробите код більш зрозумілим і стійкішим до помилок у разі змін структури.
hoijui

2
Це трохи хитро. Багато разів (деякі успішні) я намагався використовувати призначені ініціалізатори. Однак тепер уже стало зрозуміло (завдяки вашому прикладу та @ filantlant), що повинен існувати склад, якщо ініціалізатор не використовується під час створення об'єкта. Однак зараз я також дізнався, що якщо ініціалізатори використовуються в інший час, ніж створення об'єкта (як у вашому прикладі), то це позначається як складений буквальний , а не строго призначений ініціалізатор . ideone.com/Ze3rnZ
sherrellbc

93

Я бачу, ви вже отримали відповідь про ANSI C 99, тому я кину кістку про ANSI C 89. ANSI C 89 дозволяє вам ініціалізувати структуру таким чином:

typedef struct Item {
    int a;
    float b;
    char* name;
} Item;

int main(void) {
    Item item = { 5, 2.2, "George" };
    return 0;
}

Важливо пам’ятати, що коли ви ініціалізуєте навіть один об’єкт / змінну в структурі, всі інші його змінні будуть ініціалізовані до значення за замовчуванням.

Якщо ви не ініціалізуєте значення у вашій структурі, всі змінні будуть містити "значення сміття".

Удачі!


3
Чи можливо використовувати змінні при ініціалізації?
Циан

2
@Cyan Це для об'єктів з автоматичною тривалістю зберігання. Див. 6.7.9 13) у open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf . Для глобальних об'єктів в значній мірі обмежена буквами. Ви навіть не можете використовувати інші глобальні об'єкти, навіть якщо вони const.
PSkocik

Тож єдиною відмінністю C 99 є те, що ви не можете вказати позначення?
Akaisteph7


22

Ви майже отримали це ...

MY_TYPE a = { true,15,0.123 };

Швидкий пошук по 'структура ініціалізувати с' показує мені це


1
Я думаю, це справедливо для стандарту C, але не для ANSI C (99?). Також я зобов'язаний до правил кодування, які не дозволяють мені робити декларування та ініціалізацію відразу, тому мені доведеться розділити його та ініціалізувати кожну підпорядку.
Брінг

8
Ініціалізація може відбутися лише в точці декларації. Саме це означає «ініціалізувати». Інакше ви дозволяєте невизначеному значенню бути вродженим значенням змінної / структури, а потім присвоюєте значення пізніше.
Ківелі

8
гм ... У вас є правила кодування, які свідомо погані? Можливо, вам слід познайомитись із хлопцем, який написав їх на "Придбання ресурсів - це ініціалізація" ( en.wikipedia.org/wiki/RAII )
Джеймс Курран

Повірте, я справді, дійсно, не можу трохи змінити ці правила. Страшно кодувати це. І ще з 70-х існує маса інших правил, як-от статичні заголовки коду з інформацією про управління, наприклад, номер редакції. Зміни для кожного випуску, навіть якщо джерело не змінилося ...
cringe

19

Стандарт мов програмування C ISO / IEC 9899: 1999 (загальновідомий як C99) дозволяє використовувати призначений ініціалізатор для ініціалізації членів структури або об'єднання наступним чином:

MY_TYPE a = { .stuff = 0.456, .flag = true, .value = 123 };

Він визначений у paragraph 7розділі 6.7.8 Initializationстандарту ISO / IEC 9899: 1999 як:

Якщо позначення має форму
. Ідентифікатор,
то поточний об'єкт (визначений нижче) повинен мати структуру або тип об'єднання, а ідентифікатор - ім'я члена цього типу.

Зауважте, що paragraph 9в цьому ж розділі зазначено, що:

За винятком випадків, коли прямо зазначено інше, для цілей цього підпункту безіменні члени об'єктів структури та типу об'єднання не беруть участі в ініціалізації. Неназвані члени об'єктів структури мають невизначене значення навіть після ініціалізації.

У реалізації GNU GCC, опущені члени ініціалізуються як нульове або нульоподібне значення, що відповідає типу. Як зазначено в розділі 6.27 Призначені ініціалізатори документації GCC GNU:

Пропущені члени поля неявно ініціалізуються так само, як об'єкти, які мають статичну тривалість зберігання.

Компілятор Microsoft Visual C ++ повинен підтримувати призначені ініціалізатори, починаючи з версії 2013 року, згідно з офіційною доповіддю блогу C ++ на відповідність блогу . Абзац Initializing unions and structsвід ініціалізатор статті в MSDN візуальної документації студії передбачає , що неназвані члени инициализируются нульовими, як відповідні значення аналогічно GNU GCC.

Стандарт ISO / IEC 9899: 2011 (загальновідомий як C11), який витіснив ISO / IEC 9899: 1999, зберігає призначені ініціалізатори відповідно до розділу6.7.9 Initialization . Він також зберігається paragraph 9незмінним.

Новий стандарт ISO / IEC 9899: 2018 (загальновідомий як C18), який витіснив ISO / IEC 9899: 2011, зберігає призначені ініціалізатори під п 6.7.9 Initialization. Він також зберігається paragraph 9незмінним.


2
Спочатку я запропонував це змінити прийнятою відповіддю, але його відхилили. Таким чином я публікую це як відповідь.
PF4Public

Я вважаю, що "неназваний член" не означає "пропущені поля", а скоріше "анонімні члени", такі як союз у struct thing { union { char ch; int i; }; };. Пізніше стандарт говорить: "Якщо в списку, включеному дужками, є менше ініціалізаторів, ніж елементів або членів сукупності, або менше символів у рядковому букве, що використовується для ініціалізації масиву відомого розміру, ніж елементів у масиві, решта сукупності повинна бути ініціалізована неявно такою ж, як об'єкти, які мають статичну тривалість зберігання. "
emersion

14

як сказав Рон Нуні:

typedef struct Item {
    int a;
    float b;
    char* name;
} Item;

int main(void) {
    Item item = {5, 2.2, "George"};
    return 0;
}

Важливо пам’ятати: щойно ви ініціалізуєте навіть один об’єкт / змінну в структурі, всі інші його змінні будуть ініціалізовані до значення за замовчуванням.

Якщо ви не ініціалізуєте значення у вашій структурі (тобто, якщо ви просто оголосите цю змінну), усі variable.membersбудуть містити "значення сміття", лише якщо декларація є локальною!

Якщо декларація є глобальною або статичною (як у цьому випадку), усі неініціалізовані variable.membersавтоматично ініціалізуються на:

  • 0 для цілих чисел і плаваючої точки
  • '\0'для char(звичайно, це так само, як 0іchar є цілим числом)
  • NULL для покажчиків.

Цікаво, що стосується локального / глобального, це також стосується структурних значень часу. У мене був локальний, і в одному випадку DST був включений, в іншому - ні, через те, що я зробив. Я не використовував DST (поле tm_isdst), це було з strptime, але коли я перейшов у time_t, деякі були на годину.
Алан Корі

3
void function(void) {
  MY_TYPE a;
  a.flag = true;
  a.value = 15;
  a.stuff = 0.123;
}

23
як тільки я почув, що "ініціалізація відрізняється від призначення". так це не призначення, а не ініціалізація?
Hayri Uğur Koltuk

2

Якщо MS не оновлено до C99, MY_TYPE a = {вірно, 15,0.123};


2

Я знайшов інший спосіб ініціалізувати структури.

Структура:

typedef struct test {
    int num;
    char* str;
} test;

Ініціалізація:

test tt = {
    num: 42,
    str: "nice"
};

Відповідно до документації GCC, цей синтаксис є застарілим, оскільки GCC 2.5 .


2

Мені не сподобалася жодна з цих відповідей, тому я зробив свою власну. Я не знаю, це ANSI C чи ні, це просто GCC 4.2.1 в режимі за замовчуванням. Я ніколи не пам'ятаю брекетинг, тому я починаю з підмножини своїх даних і борюся з повідомленнями про помилки компілятора, поки воно не закриється. Читання - мій перший пріоритет.

    // in a header:
    typedef unsigned char uchar;

    struct fields {
      uchar num;
      uchar lbl[35];
    };

    // in an actual c file (I have 2 in this case)
    struct fields labels[] = {
      {0,"Package"},
      {1,"Version"},
      {2,"Apport"},
      {3,"Architecture"},
      {4,"Bugs"},
      {5,"Description-md5"},
      {6,"Essential"},
      {7,"Filename"},
      {8,"Ghc-Package"},
      {9,"Gstreamer-Version"},
      {10,"Homepage"},
      {11,"Installed-Size"},
      {12,"MD5sum"},
      {13,"Maintainer"},
      {14,"Modaliases"},
      {15,"Multi-Arch"},
      {16,"Npp-Description"},
      {17,"Npp-File"},
      {18,"Npp-Name"},
      {19,"Origin"}
    };

Дані можуть почати життя як файл з обмеженими вкладками, який ви шукаєте-замінюєте, щоб зробити масаж у щось інше. Так, це речі Debian. Отже одна зовнішня пара {} (із зазначенням масиву), потім ще одна пара для кожної структури всередині. З комами між. Розміщення речей у заголовку не є строго необхідним, але у мене в структурі близько 50 елементів, тому я хочу, щоб вони були в окремому файлі, обидва не дозволяли заплутатись від мого коду, і тому його легше замінити.


3
Про ініціалізацію масиву структур не виникало питань! Я маю на увазі, ваша відповідь містить накладні витрати.
Віктор Сигнаєвський

Я думаю, що моя думка полягала в оголошенні структури зі значеннями, які вже є в ній, використовуючи = для встановлення кожного значення пізніше.
Алан Корі

Цей принцип застосовується, будь то одна структура або їх масив. Використання = насправді не ініціалізація, я б не думав.
Алан Корі

0

Структуру в C можна оголосити та ініціалізувати так:

typedef struct book
{
    char title[10];
    char author[10];
    float price;
} book;

int main() {
    book b1={"DS", "Ajay", 250.0};

    printf("%s \t %s \t %f", b1.title, b1.author, b1.price);

    return 0;
}

0

Я ще прочитав Документацію Microsoft Visual Studio 2015 для ініціалізації типів сукупності, всі форми ініціалізації з{...} пояснення пояснюються, але ініціалізація з крапкою під назвою '' позначення '' там не згадується. Це також не працює.

Стандартний стандарт C99 в розділі 6.7.8 Ініціалізація пояснює можливість позначень, але, на мій погляд, це не зовсім зрозуміло для складних структур. Стандарт C99 у форматі PDF .

На мій погляд, це може бути краще

  1. Використовуйте = {0};-ініціалізацію для всіх статичних даних. Це менше зусиль для машинного коду.
  2. Наприклад, використовуйте макроси для ініціалізації

    typedef MyStruct_t{ int x, int a, int b; } MyStruct; define INIT_MyStruct(A,B) { 0, A, B}

Макрос можна адаптувати, його список аргументів може бути незалежним від зміненого змісту структури. Це правильно, якщо слід ініціалізувати менше елементів. Він також підходить для вкладеної структури. 3. Проста форма: ініціалізація в підпрограмі:

void init_MyStruct(MyStruct* thiz, int a, int b) {
  thiz->a = a; thiz->b = b; }

Ця рутина виглядає як ObjectOriented в C. Використовуйте thiz, а не thisкомпілювати її з C ++!

MyStruct data = {0}; //all is zero!
init_MyStruct(&data, 3, 456);

0

Я шукав хороший спосіб ініціалізувати свою структуру, і мені довелося скористатися наведеним нижче способом (C99). Це дозволяє мені ініціалізувати або одну структуру, або масив структур так само, як і прості типи.

typedef struct {
    char *str;
    size_t len;
    jsmntok_t *tok;
    int tsz;
} jsmn_ts;

#define jsmn_ts_default (jsmn_ts){NULL, 0, NULL, 0}

Це може використовуватися в коді як:

jsmn_ts mydata = jsmn_ts_default; /* initialization of a single struct */

jsmn_ts myarray[10] = {jsmn_ts_default, jsmn_ts_default}; /* initialization of
                                                    first 2 structs in the array */

Але це не ініціалізує масив структур до значень за замовчуванням. Він ініціалізує першу структуру в масиві до значень, заданих у jsmn_ts_default, але решта структур ініціалізуються так, як якщо б масив мав статичну тривалість зберігання, це означає, що члени ініціалізовані до NULLта 0. Але якщо ви змінитесь, #define jsmn_ts_default (jsmn_ts){"default", sizeof "default", NULL, 0}ви побачите, що тільки перший елемент масиву стає таким ініціалізованим.
ex nihilo

Так, я щойно повернувся з ще тестування, хотів відредагувати допис :) btw, така ж поведінка трапляється int a[10] = {1};лише з 1-м елементом буде==1
div0man
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.