在C语言中,初始化是为变量赋予初始值的过程。初始化列表是一种特殊且强大的语法构造,允许我们在声明变量的同时,以一种简洁明了的方式为其成员或元素赋初值。它尤其适用于初始化数组、结构体和联合体等聚合类型。

什么是C初始化列表?

C初始化列表(Initializer List)是一系列用逗号分隔的表达式,这些表达式被花括号 `{}` 包围,用于在声明一个变量时为其赋初值。其基本形式如下:

类型 变量名 = { 值1, 值2, 值3, ... };

初始化列表的出现,使得复杂数据类型的初始化变得更加直观和方便。

为什么要使用初始化列表?

使用初始化列表有几个核心优势:

  • 简洁与清晰: 相较于声明后逐个赋值,初始化列表将初始化过程与变量声明合并,代码更紧凑,意图更明确。尤其对于数组或结构体,可以一次性设定其初始状态。
  • 聚合类型的便利: 它是初始化整个数组或结构体的最常用和最直接的方式。
  • 部分初始化与默认值: 当提供的初始化值数量少于聚合类型的成员或元素数量时,剩余的成员或元素会根据存储类型自动进行默认初始化(通常为零值或空指针)。这是一种方便的“全部清零”或“部分设置”的方式。
  • 编译时检查: 初始化列表的格式和元素数量会在编译时进行检查,有助于发现错误。如果提供的初始化值太多,编译器会报错。

初始化列表可以用在哪里?

初始化列表主要用于初始化以下类型的变量:

1. 数组 (Arrays)

可以用来初始化整型数组、浮点型数组、字符数组,甚至是结构体数组或指针数组。

int primes[] = { 2, 3, 5, 7, 11 }; // 数组大小由初始化列表确定
float temps[5] = { 98.6, 99.1, 100.0 }; // 数组大小指定,只初始化前3个,后2个为0.0
char greeting[] = "Hello"; // 字符串字面量是初始化char数组的一种特殊方式,等价于 {'H', 'e', 'l', 'l', 'o', '\0'}

2. 结构体 (Structs)

可以用来初始化结构体的成员,按照结构体成员的声明顺序进行。

struct Point { int x; int y; };
struct Point origin = { 0, 0 }; // 初始化x为0,y为0

对于嵌套结构体,可以使用嵌套的初始化列表:

struct Rect { struct Point p1; struct Point p2; };
struct Rect unit_rect = { {0, 0}, {1, 1} }; // 初始化p1为{0,0},p2为{1,1}

3. 联合体 (Unions)

标准的初始化列表语法只能用来初始化联合体的第一个成员

union Data { int i; float f; char* s; };
union Data d = { 123 }; // 初始化第一个成员 i 为 123

如果想初始化联合体的其他成员,需要使用C99引入的“指定初始化器”(Designated Initializers)。

4. 变量的存储时长 (Storage Duration)

初始化列表可以用于具有不同存储时长的变量:

  • 静态存储时长 (Static Storage Duration): 包括全局变量和使用 `static` 关键字声明的变量。这类变量在程序启动时初始化。C89标准要求初始化列表中的表达式必须是常量表达式。C99标准放宽了这一限制,允许使用非常量表达式,但通常只针对自动变量。对于静态/全局变量,如果使用不完整的初始化列表,剩余的成员/元素会自动初始化为零值(整数为0,浮点数为0.0,指针为NULL)。
  • 自动存储时长 (Automatic Storage Duration): 函数内部不带 `static` 关键字声明的局部变量。这类变量在进入其作用域时创建和初始化。C89标准不允许使用初始化列表,只能声明后赋值。C99标准允许使用初始化列表,并且列表中的表达式可以是任意表达式(包括非常量表达式)。对于自动变量,如果使用不完整的初始化列表,剩余的成员/元素的值是不确定的(indeterminate),除非这些剩余元素是由于列表的最后一个元素初始化了一个聚合体而导致的零值填充。

总结一下不完整初始化的默认值:
– 静态/全局变量:剩余部分总是零初始化。
– 自动变量:剩余部分值不确定,除非是最后一个初始化项引起的尾部填充(这部分会零填充)。

初始化列表中可以有多少值?

初始化列表中的值的数量是有限制的:

  • 如果初始化一个确定大小的聚合类型(如 `int arr[5]` 或一个结构体),初始化列表中的值的数量不能超过该聚合类型成员或元素的总数。如果超过,编译器会报错。
  • 如果初始化一个大小不确定的数组(如 `int arr[]`),数组的大小将由初始化列表中的值的数量决定。
  • 如果初始化列表中的值的数量少于聚合类型成员或元素的总数,剩余的成员或元素会根据上述规则(静态/全局变量零初始化,自动变量值不确定)进行处理。

如何使用初始化列表?具体语法与示例

除了上面提到的基本语法,C语言(尤其是C99及其后续标准)提供了更灵活的初始化方式:指定初始化器 (Designated Initializers)

使用指定初始化器 (C99+)

指定初始化器允许你按名称(对结构体和联合体)或按索引(对数组)来指定要初始化的成员或元素,而不必遵循其声明的顺序。这提高了代码的可读性和健壮性(即使成员/元素的顺序改变,初始化代码也不需要修改)。

数组的指定初始化器: `[索引] = 值`

int arr[10] = { [0] = 10, [5] = 50, [9] = 90 };

这个例子初始化了 `arr[0]` 为 10, `arr[5]` 为 50, `arr[9]` 为 90。其他未指定的元素会根据存储类型进行零初始化或值不确定。

你也可以结合使用指定初始化器和非指定初始化器:

int arr[10] = { 1, 2, [5] = 50, 60, [9] = 90 };

这里的规则是:非指定初始化器(如 1, 2, 60)会初始化当前指定位置的下一个未被初始化的元素。例如:
{ 1, 2, [5]=50, 60, [9]=90 } 的初始化顺序和值是:
– `1` 初始化 `arr[0]`
– `2` 初始化 `arr[1]` (arr[0]的下一个)
– `[5]=50` 初始化 `arr[5]`
– `60` 初始化 `arr[6]` (arr[5]的下一个)
– `[9]=90` 初始化 `arr[9]`
未指定的元素(arr[2], arr[3], arr[4], arr[7], arr[8])将被默认初始化。

结构体的指定初始化器: `.成员名 = 值`

struct Point p = { .y = 20, .x = 10 };

这个例子先初始化 `p.y` 为 20,然后初始化 `p.x` 为 10。顺序可以随意。使用指定初始化器尤其适合初始化结构体中只有少数几个成员的情况。

struct Rect r = { .p2.x = 100, .p1.y = 50 }; // 初始化嵌套结构体成员

联合体的指定初始化器 (C99+)

指定初始化器允许初始化联合体的任何一个成员:

union Data d = { .f = 3.14f }; // 初始化成员 f

初始化列表的规则与注意事项

  • 顺序: 使用非指定初始化器时,初始化值严格按照聚合类型声明的顺序进行赋值。使用指定初始化器时,顺序由指定符决定,未指定的部分则按声明顺序从最后一个指定或初始化位置的“下一个”开始默认初始化。
  • 常量表达式: 在C89中,用于初始化具有静态存储时长变量的初始化列表中的表达式必须是常量表达式。C99及以后标准对此有所放宽。
  • 不完整列表: 如前所述,不完整的初始化列表会导致剩余部分被默认初始化(零值或值不确定,取决于存储时长)。这是一个常用技巧,例如 `int arr[100] = {0};` 可以将整个100个元素的数组全部初始化为0。注意,`int arr[100] = {};` 在C99+中也是合法的,效果与 `int arr[100] = {0};` 相同,都是全部零初始化。
  • 字符串字面量: 使用字符串字面量初始化 `char` 数组是一个特例。`char arr[] = “abc”;` 会创建一个大小为4的数组(包含终止符 `\0`),并将字符串内容复制进去。如果指定了数组大小且小于字符串长度,则会发生溢出或截断(取决于标准版本和编译器,但通常会警告)。如果大小刚好等于字符串长度,则不会包含终止符。例如 `char arr[3] = “abc”;` 初始化 `arr` 为 {‘a’, ‘b’, ‘c’},没有终止符。

总结

C语言的初始化列表是初始化数组、结构体和联合体等聚合类型变量的强大工具。它提供了简洁、清晰且灵活的初始化方式。通过非指定初始化器,可以按顺序初始化成员;通过C99引入的指定初始化器,可以按名称或索引灵活地初始化特定成员。理解初始化列表的行为,特别是关于不完整初始化和不同存储时长变量的默认行为,对于编写健壮可靠的C代码至关重要。


c初始化列表

By admin

发表回复