当前位置: C语言 -- 基础 -- 声明

声明(十八)

十一、初始化

初始化是使用初始化器指定对象中存储的初始值。初始化器具有以下两种语法格式:

assignment-expression
{ initializer_listopt }

其中assignment-expression是赋值表达式;initializer_list是可选的初始化器列表。如果不存在初始化器列表,空的大括号对({ })称为空初始化器(empty initializer),对应的初始化称为空初始化(empty initialization)。

int n = 3;		//使用赋值表达式初始化。
int a1[] = {3, 6, 9};	//使用初始化器列表初始化。
int a2[n] = { };	//空初始化。

:空初始化是ISO/IEC 9899:2024标准新增内容。)


ISO/IEC 9899:2024标准对初始化作了一些限制:

-- 任何初始化器都不会为初始化实体外的对象提供值。

int a1[3] = {1, 3, 5, 7};
int a2[3];

实现中数组a1和数组a2在内存位置上可能相邻;但不会用数组a1中多余的初始化器去初始化数组a2中的元素。


-- 初始化实体的类型应为未知大小的数组类型或者完整对象类型。

const char country[] = "China";	//合法。
int (*ptr)(int) = NULL;		//合法。
int n = 3;				//合法。
int arr[3] = {3, 5, 9};		//合法。

-- 变长数组类型的实体不能使用空初始化器以外的初始化器初始化。

int n = 3;

int a1[n] = {}; 	//合法。
int a2[n] = {3, 6, 9};	//非法。
int a3[n] = {0};	//非法。

-- 未知大小的数组不能使用空初始化器初始化。

int a1[] = { };		//非法。
int a2[] = {3, 6, 9};	//合法。

-- 对于具有静态存储期限或者线程存储期限,或者使用constexpr存储类说明符声明的对象,初始化器中的所有表达式应为常量表达式或者字符串字面量。

int n = 3;

static int i1 = 3;  //合法。
static int i2 = n;  //非法。

constexpr int ci1 = 3;  //合法。
constexpr int ci2 = n;  //非法。

-- 如果标识符具有块作用域,并且该标识符具有外部链接或者内部链接,则该标识符不能初始化。

static int i; //i具有内部链接。
...
{
  extern int i = 5; //非法,i具有块作用域。
}

具有自动存储期限的对象如果未显式初始化,其表示形式是不确定的。具有静态存储期或者线程存储期的对象如果未显式初始化,以及任何使用空初始化器初始化的对象,将执行默认初始化(default initialization)。

默认初始化规则:

-- 如果具有指针类型,将初始化为空指针。

static int *p1; //等价于static int *p1 = nullptr;。
int *p2 = {};   //等价于int *p2 = nullptr;。

-- 如果具有十进制浮点类型(decimal floating type),将初始化为+0,并且量子指数(quantum exponent)由实现定义。

static _Decimal64 dd;   //dd值为+0。

-- 如果具有算术类型,并且不是十进制浮点类型,将初始化为(正数或者无符号)0。

static int i1;  //等价于static int i1 = 0;。
int i2 = {};    //等价于int i2 = 0;。

-- 如果具有聚合类型(aggregate type),每个成员将根据上述规则初始化,并且任何填充部分将初始化为0位。

static int arr[3];  //数组元素值均为0。

struct {
    int i;
    double d;
} ss = {};  //结构成员值均为0。

-- 如果具有联合类型,将根据上述规则对第一个成员初始化,并且任何填充部分将初始化为0位。

union {
    int i;
    double d;
} uu = {};  //uu.i值为0。

标量初始化的初始化器应为单个表达式(可以存在大括号,也可以不存在大括号。)或者空初始化器。如果初始化器不是空初始化器,则表达式值(转换后)是对象的初始值;简单赋值的类型约束和转换规则适用于初始化,此时标量类型视为其声明类型的未限定版本。

int i1 = 3;
int i2 = {};
int i3 = { 5 };

1、结构和联合的初始化

结构或者联合的初始化器可以是具有结构类型或者联合类型的单一表达式,也可以是初始化器列表。

:除非另有明确说明,否则结构和联合中的未命名成员不参与初始化。结构中未命名成员即使初始化后也是具有不确定表示形式。)

struct s{
    int i;
    double d;
};

struct s s1 = {5, 3.14};	//使用初始化器列表初始化。
struct s s2 = s1;		//使用单一表达式初始化。

使用单一表达式初始化时,对象初始值(包括未命名成员)是表达式值。


使用初始化器列表初始化时应使用大括号({ })。每个使用大括号括起来的初始化器列表都与当前对象(current object)相关联。如果不存在指定初始化,结构成员按声明顺序初始化,对于联合则初始化第一个命名成员。

struct {
  int i;
  double d;
} ss = {5, 3.14}; //ss.i值为5,ss.d值为3.14。

union {
  int : 4;
  int i;
  double d;
} uu = {5}; //uu.i值为5。

如果子结构或者包含联合的初始化器列表未以左大括号开头,则其子对象将按常规方式初始化(即结构成员按声明顺序初始化,联合初始化第一个命名成员。),但该子结构或者包含的联合不会成为当前对象:当前对象仅与大括号包含的初始化器列表相关联。

typedef struct tag{
  int i;
  double d;
} S;

struct s{
  S s1;
  S s2;
};

//{1, 1.23, 2, 2.23}关联的当前对象是a。
struct s a = {1, 1.23, 2, 2.23};

//{{3, 3.14},{4, 4.14}}关联的当前对象是b。
//{3, 3.14}关联的当前对象是b.s1。
//{4, 4.14}关联的当前对象是b.s2。
struct s b = {{3, 3.14},{4, 4.14}};

联合成员初始化后,下一个初始化对象并非联合的下一个成员,而是包含该联合的对象的下一个子对象。

typedef union u {
  int i;
  double d;
} U;

struct s {
  U uu;
  double d;
} ss = {5, 3.14}; //ss.uu.i值为5;ss.d值为3.14。

对于结构成员或者联合成员也可以指定初始化,指定初始化的指定符具有以下语法格式:

. identifier

当前对象应具有结构类型或者联合类型;标识符(identifier)应为结构或者联合成员名。

struct s{
  int i;
  double d;
} ss = {.d=3.14}; //ss.d的值为3.14。

union u{
  int i;
  double d;
} uu = {.d=3.14}; //uu.d的值为3.14。

如果存在指定初始化,指定符会导致后续初始化器初始化由指定符描述的子对象;随后初始化从指定符描述的子对象后的下一个子对象开始按顺序继续初始化。

struct s{
  char ch;
  int i;
  double d;
};

//ss.i值为3;ss.d值为3.14。
struct s ss = {.i=3, 3.14};

每个指定符列表均以与当前对象相关的最近花括号对开始。指定符列表中的每个列表项按顺序指定当前对象的特定成员,并将下一个指定符(如果存在。)的当前对象更改为该成员。指定符列表结束生成的当前对象是后续初始化器初始化的子对象。

typedef struct tag{
  int i;
  double d;
} S;

struct s{
  S s1;
  S s2;
  double d;
};

//ss.s1.d值为1.11。
//ss.s2.d值为2.22。
//ss.d值为3.33。
struct s ss = {{.d=1.11}, {.d=2.22}, .d=3.33};

一个指定符只能指定一个相关联的子对象;独立的指定符列表相互之间是独立的。


初始化按初始化器列表顺序进行,为特定子对象提供的每个初始化器将覆盖之前为同一子对象列出的任何初始化器;所有未显式初始化的子对象将执行默认初始化。如果初始化器列表中的初始化器少于结构成员,结构中的剩余结构成员将执行默认初始化。

struct s{
  int i1;
  double d1;
  int i2;
  double d2;
};

//ss.i1值为3。
//ss.d1值为0.0。
//ss.i2值为5。
//ss.d2值为0.0。
struct s ss = {3, .i2=5};

任何用于初始化子对象的初始化器,如果被覆盖且因此未用于初始化子对象,可能不会被评估。

struct s{
  int i1;
  int i2;
  int i3;
  int i4;
};

int n = 3;
struct s ss = {2, n++, .i2=5};	//表达式n++可能不会被评估。

如果结构或者联合包含聚合(aggregate)或者联合作为成员,上述规则可递归用于子聚合或者包含的联合。如果子聚合或者包含的联合的初始化器以{开头,匹配的}包含的初始化器将初始化子聚合或者包含的联合的元素或者成员;否则仅从初始化器列表中取足够的初始化器来初始化子聚合或者包含的联合的第一个成员;剩余初始化器将用于初始化当前子聚合或者包含联合所属的聚合的下一个元素或成员。

typedef struct tag{
  int i;
  double d;
} S;

struct s{
  S s1;
  S s2;
};

//ss.s1.i值为3。
//ss.s1.d值为3.14。
//ss.s2.i值为4。
//ss.s2.d值为0.0。
struct s ss = {{3, 3.14},{4}};

初始化器列表表达式相互之间的评估顺序是不确定的,因此任何副作用发生的顺序是未指定的。

struct s{
  int i1;
  int i2;
  int i3;
};

int m = 2;
int n = 4;

//ss.i3值可能是6、7或者8。
struct s ss = {m++, n++, m+n};

2、数组的初始化

字符类型数组可以使用字符串字面量或者UTF-8字符串字面量初始化(可以存在大括号,也可以不存在大括号。)。字符串字面量的连续字节(包括终止空字符,如果数组空间允许或者数组大小未知。)将初始化数组元素。

char a1[6] = "China";
char a2[] = { "hello" };
char8_t a8[] = u8"lucky";

char8_tISO/IEC 9899:2024标准新增类型,ISO/IEC 9899:2024标准之前的ISO标准使用char类型。)


元素类型与限定的或者非限定的wchar_tchar16_t或者char32_t类型兼容的数组,可以使用对应编码前缀(Lu或者U)的宽字符串字面量初始化(可以存在大括号,也可以不存在大括号。)。宽字符串字面量中的连续宽字符(包括结尾宽空字符,如果数组空间允许或者数组大小未知。)将初始化数组元素。

wchar_t warr[] = L"一路顺风";
char16_t arr16[] = { u"琴棋书画" };
char32_t arr32[] = U"旭日东升";

除上述情况外,数组对象初始化时初始化器列表应使用大括号({ })。每个使用大括号括起来的初始化器列表都与当前对象(current object)相关联。如果不存在指定初始化,数组元素按下标递增顺序初始化。

int arr[3] = {3, 5, 7};  //数组元素arr[0]、arr[1]、arr[2]值分别为3、5、7。

如果子对象的初始化器列表未以左大括号开头,则其子对象将按常规方式初始化(即数组元素按下标递增顺序初始化。),但该子对象不会成为当前对象:当前对象仅与大括号包含的初始化器列表相关联。

//{3, 5, 7, 2, 4, 6}关联的当前对象是a1。
int a1[2][3] = {3, 5, 7, 2, 4, 6};

//{{1, 2, 3},{4, 5, 6}}关联的当前对象是a2。
//{1, 2, 3}关联的当前对象是a2[0]。
//{4, 5, 6}关联的当前对象是a2[1]。
int a2[2][3] = {{1, 2, 3},{4, 5, 6}};

数组元素也可以指定初始化,指定初始化的指定符具有以下语法格式:

[ constant-expression ]

当前对象应具有数组类型,constant-expression应为整数常量表达式;如果数组大小未知,constant-expression可以为任意非负整数值。

//{[0][1]=3}关联的当前对象是a1,a1[0][1]值为3。
int a1[2][3] = {[0][1]=3};  

//{[1]=5}关联的当前对象是a2[0],a2[0][1]值为5。
int a2[2][3] = {{[1]=5}};

如果存在指定初始化,指定符会导致后续初始化器初始化由指定符描述的子对象;随后初始化从指定符描述的子对象后的下一个子对象开始按顺序继续初始化。

int arr[5] = {[2]=6, 8};  //arr[2]、arr[3]值分别为6、8,其余数组元素值为0。

每个指定符列表均以与当前对象相关的最近花括号对开始。指定符列表中的每个列表项按顺序指定当前对象的特定成员,并将下一个指定符(如果存在。)的当前对象更改为该成员。指定符列表结束生成的当前对象是后续初始化器初始化的子对象。

//arr[0][1]、arr[1][1]、arr[2][1]值分别为3、6、9。
int arr[3][3] = {{[1]=3},{[1]=6}, [2][1]=9};

一个指定符只能指定一个相关联的子对象;独立的指定符列表相互之间是独立的。


初始化按初始化器列表顺序进行,为特定子对象提供的每个初始化器将覆盖之前为同一子对象列出的任何初始化器;所有未显式初始化的子对象将执行默认初始化。如果初始化列表中的初始化器少于数组元素,数组中的剩余数组元素将执行默认初始化。

//arr[0]值为3。
//arr[1]值为5。
//其余数组元素值为0。
int arr[5] = {3, 5};

任何用于初始化子对象的初始化器,如果被覆盖且因此未用于初始化子对象,可能不会被评估。

int n = 3;
int arr[] = {2, n++, [1]=7};  //表达式n++可能不会被评估。

如果数组包含聚合(aggregate)或者联合作为成员,上述规则可递归用于子聚合或者包含的联合。如果子聚合或者包含的联合的初始化器以{开头,匹配的}包围的初始化器将初始化子聚合或者包含的联合的元素或者成员;否则仅从初始化列表中取足够的初始化器来初始化子聚合或者包含的联合的第一个成员;剩余初始化器将用于初始化当前子聚合或者包含联合所属的聚合的下一个元素或成员。

typedef struct {
  int i;
  double d;
} S;

//arr[0].d、arr[1].d、arr[2].d的值分别为1.14、2.14、3.14。
S arr[3] = {{.d=1.14},{.d=2.14},[2].d=3.14};

未知大小的数组初始化时,数组大小由显式初始化的具有最大数组下标的数组元素决定。数组类型在初始化器列表结束时是完整类型。

char a1[] = "China";		//存在6个数组元素。
int a2[] = { [10]=5 };		//存在11个数组元素。
int a3[] = { [5]=3, 8 };	//存在7个数组元素。

typedef int ARR[];
ARR a4 = { 1, 2, 3 };		//存在3个数组元素。

初始化器列表表达式相互之间的评估顺序是不确定的,因此任何副作用发生的顺序是未指定的。

int m = 2;
int n = 4;

//arr[2]值可能是6、7或者8。
int arr[3] = {m++, n++, m+n};