命名空间
变体
操作

结构体和联合体初始化

来自 cppreference.com
< c‎ | 语言

初始化 结构体联合体 类型对象时,初始化器必须是一个非空的(直到 C23)花括号括起来的、逗号分隔的成员初始化器列表

= { 表达式 , ... } (1) (直到 C99)
= { 指定符(可选) 表达式 , ... } (2) (自 C99 起)
= { } (3) (自 C23 起)

其中 指定符 是一个(空格分隔或相邻的)单个成员指定符序列,形式为 . 成员数组指定符,形式为 [ 索引 ]

所有未显式初始化的成员都将进行 空初始化

内容

[编辑] 解释

在初始化 联合体 时,初始化器列表必须只有一个成员,该成员初始化联合体的第一个成员,除非使用指定初始化器(自 C99 起)

union { int x; char c[4]; }
  u = {1},           // makes u.x active with value 1
 u2 = { .c={'\1'} }; // makes u2.c active with value {'\1','\0','\0','\0'}

在初始化 结构体 时,列表中的第一个初始化器初始化第一个声明的成员(除非指定了指定符)(自 C99 起),所有随后的初始化器(不带指定符)(自 C99 起)初始化在由前一个表达式初始化的成员之后声明的结构体成员。

struct point {double x,y,z;} p = {1.2, 1.3}; // p.x=1.2, p.y=1.3, p.z=0.0
div_t answer = {.quot = 2, .rem = -1 };      // order of elements in div_t may vary

指定符会导致以下初始化器初始化由指定符描述的结构体成员。然后初始化按声明顺序继续进行,从指定符描述的成员之后声明的下一个元素开始。

struct {int sec,min,hour,day,mon,year;} z
   = {.day=31,12,2014,.sec=30,15,17}; // initializes z to {30,15,17,31,12,2014}
(自 C99 起)

提供超过成员数量的初始化器是错误的。

[编辑] 嵌套初始化

如果结构体或联合体的成员是数组、结构体或联合体,则花括号括起来的初始化器列表中的相应初始化器是任何对这些成员有效的初始化器,除了它们的括号可以按如下方式省略:

如果嵌套初始化器以左花括号开始,则从左花括号开始到右花括号结束的整个嵌套初始化器将初始化相应的成员对象。每个左花括号都会建立一个新的当前对象。当前对象的成员按其自然顺序进行初始化,除非使用指定符(自 C99 起):数组元素按下标顺序,结构体成员按声明顺序,任何联合体中仅声明的第一个成员。当前对象中未被右花括号显式初始化的子对象将进行 空初始化

struct example {
    struct addr_t {
       uint32_t port;
    } addr;
    union {
       uint8_t a8[4];
       uint16_t a16[2];
    } in_u;
};
struct example ex = { // start of initializer list for struct example
                     { // start of initializer list for ex.addr
                        80 // initialized struct's only member
                     }, // end of initializer list for ex.addr
                     { // start of initializer-list for ex.in_u
                        {127,0,0,1} // initializes first element of the union
                     } };

如果嵌套初始化器不以左花括号开始,则只会从列表中提取足够多的初始化器来考虑成员数组、结构体或联合体的元素或成员;任何剩余的初始化器将被保留以初始化下一个结构体成员

struct example ex = {80, 127, 0, 0, 1}; // 80 initializes ex.addr.port
                                        // 127 initializes ex.in_u.a8[0]
                                        // 0 initializes ex.in_u.a8[1]
                                        // 0 initializes ex.in_u.a8[2]
                                        // 1 initializes ex.in_u.a8[3]

当指定符嵌套时,成员的指定符遵循封闭结构体/联合体/数组的指定符。在任何嵌套的方括号括起来的初始化器列表中,最外层的指定符引用当前对象,并且仅选择要在当前对象中初始化的子对象。

struct example ex2 = { // current object is ex2, designators are for members of example
                       .in_u.a8[0]=127, 0, 0, 1, .addr=80}; 
struct example ex3 = {80, .in_u={ // changes current object to the union ex.in_u
                           127,
                           .a8[2]=1 // this designator refers to the member of in_u
                      } };

如果任何子对象被显式初始化两次(这在使用指定符时可能会发生),则列表中出现的较后初始化器将被使用(较早的初始化器可能不会被评估)

struct {int n;} s = {printf("a\n"), // this may be printed or skipped
                     .n=printf("b\n")}; // always printed

虽然任何未初始化的子对象都会被隐式初始化,但如果子对象的显式初始化出现在初始化器列表的前面,则隐式初始化子对象永远不会覆盖对同一子对象的显式初始化(选择 clang 以观察正确的输出)

#include <stdio.h>
typedef struct { int k; int l; int a[2]; } T;
typedef struct { int i;  T t; } S;
T x = {.l = 43, .k = 42, .a[1] = 19, .a[0] = 18 };
 // x initialized to {42, 43, {18, 19} }
int main(void)
{
    S l = { 1,          // initializes l.i to 1
           .t = x,      // initializes l.t to {42, 43, {18, 19} }
           .t.l = 41,   // changes l.t to {42, 41, {18, 19} }
           .t.a[1] = 17 // changes l.t to {42, 41, {18, 17} }
          };
    printf("l.t.k is %d\n", l.t.k); // .t = x sets l.t.k to 42 explicitly
                                    // .t.l = 41 would zero out l.t.k implicitly
}

输出

l.t.k is 42

但是,当初始化器以左花括号开始时,其当前对象将被完全重新初始化,并且任何之前针对其子对象的显式初始化器都将被忽略

struct fred { char s[4]; int n; };
struct fred x[ ] = { { { "abc" }, 1 }, // inits x[0] to { {'a','b','c','\0'}, 1 }
                      [0].s[0] = 'q'   // changes x[0] to { {'q','b','c','\0'}, 1 }
                   };
struct fred y[ ] = { { { "abc" }, 1 }, // inits y[0] to { {'a','b','c','\0'}, 1 }
                     [0] = { // current object is now the entire y[0] object
                             .s[0] = 'q' 
                            } // replaces y[0] with { {'q','\0','\0','\0'}, 0 }
                    };
(自 C99 起)

[编辑] 注释

初始化器列表可以有一个尾随逗号,该逗号将被忽略。

struct {double x,y;} p = {1.0,
                          2.0, // trailing comma OK
                          };

在 C 中,花括号括起来的初始化器列表不能为空(注意 C++ 允许空列表,并注意 C 中的 结构体 不能为空)

(直到 C23)

在 C 和 C++ 中,初始化器列表可以为空

(自 C23 起)
struct {int n;} s = {0}; // OK
struct {int n;} s = {}; // Error until C23: initializer-list cannot be empty
                        // OK since C23: s.n is initialized to 0
struct {} s = {}; // Error: struct cannot be empty

在初始化任何存储期的聚合体时,初始化器列表中的每个表达式都必须是 常量表达式

(直到 C99)

与所有其他 初始化 一样,在初始化静态或线程局部(自 C11 起) 存储期 的聚合体时,初始化器列表中的每个表达式都必须是 常量表达式

static struct {char* p} s = {malloc(1)}; // error

初始化器中任何子表达式的 求值顺序 是不确定的(但在 C++ 中自 C++11 起并非如此)

int n = 1;
struct {int x,y;} p = {n++, n++}; // unspecified, but well-defined behavior:
                                  // n is incremented twice in arbitrary order
                                  // p equal {1,2} and {2,1} are both valid
(自 C99 起)

[编辑] 示例

#include <stdio.h>
#include <time.h>
 
int main(void)
{
    char buff[70];
    // designated initializers simplify the use of structs whose
    // order of members is unspecified
    struct tm my_time = { .tm_year=2012-1900, .tm_mon=9, .tm_mday=9,
                          .tm_hour=8, .tm_min=10, .tm_sec=20 };
    strftime(buff, sizeof buff, "%A %c", &my_time);
    puts(buff);
}

可能的输出

Sunday Sun Oct  9 08:10:20 2012

[编辑] 参考文献

  • C17 标准 (ISO/IEC 9899:2018)
  • 6.7.9/12-39 初始化 (p: 101-105)
  • C11 标准 (ISO/IEC 9899:2011)
  • 6.7.9/12-38 初始化 (p: 140-144)
  • C99 标准 (ISO/IEC 9899:1999)
  • 6.7.8/12-38 初始化 (p: 126-130)
  • C89/C90 标准 (ISO/IEC 9899:1990)
  • 6.5.7 初始化

[编辑] 另请参阅

C++ 文档 用于 聚合初始化