数组初始化
初始化数组类型的对象时,初始化器必须是一个字符串字面量(可选地在花括号中),或是一个花括号环绕的被初始化数组成员列表:
= 字符串字面量
|
(1) | ||||||||
= { 表达式 , ... }
|
(2) | (C99 前) | |||||||
= { 指派符(可选) 表达式 , ... }
|
(2) | (C99 起) | |||||||
= { }
|
(3) | (C23 起) | |||||||
[ 常量表达式 ] = 的数组指派符(C99 起)可初始化已知大小的数组和未知大小的数组,但不可初始化 VLA(C99 起)(C23 前)。只能空初始化 VLA。(C23 起)
任何未被显式初始化的数组元素均被空初始化。
从字符串初始化
字符串字面量(可选地在花括号中)可以用作匹配数组类型的初始化器:
- 通常字符串字面量及 UTF-8 字符串字面量(C11 起)可用于初始化任何字符类型(
char、signed char、unsigned char)的数组。 - L 前缀的宽字符串字面量可用于初始化任何与 wchar_t 兼容(忽略 cv 限定)的类型的数组。
|
(C11 起) |
字符串字面量的连续字节,或宽字符串字面量的连续宽字符,包含空终止字节/字符,会初始化数组的元素:
char str[] = "abc"; // str 拥有类型 char[4] 并保有 'a', 'b', 'c', '\0'
wchar_t wstr[4] = L"猫"; // wstr 拥有类型 wchar_t[4] 并保有 L'猫', L'\0', L'\0', L'\0'
若数组大小已知,则它可以比字符串字面量的大小少一,此情况下空终止字符被忽略:
char str[3] = "abc"; // str 拥有类型 char[3] 并保有 'a', 'b', 'c'
注意这种数组的内容是可更改的,不同于直接以 char* str = "abc"; 访问字符串常量。
从花括号环绕列表初始化
当数组以花括号环绕的初始化器列表初始化时,首个初始化器初始化序号为零的数组元素(除非指定了指派符)(C99 起),而每个不含指派符的(C99 起)后继的初始化器所初始化的数组元素,序号比前一个初始化器所初始化的多一。
int x[] = {1,2,3}; // x 拥有类型 int[3] 并保有 1,2,3
int y[5] = {1,2,3}; // y 拥有类型 int[5] 并保有 1,2,3,0,0
int z[4] = {1}; // z 拥有类型 int[4] 并保有 1,0,0,0
int w[3] = {0}; // w 拥有类型 int[3] 并保有全零
在初始化已知大小数组时,提供多于元素数量的初始化器是错误(除了从字符串字面量初始化字符数组)。
|
指派符导致接下来的初始化器初始化指派符所描述的数组元素。然后初始化以向前顺序持续,从指派符所描述元素的下一个元素开始。 int n[5] = {[4]=5,[0]=1,2,3,4}; // 保有 1,2,3,4,5
int a[MAX] = { // 开始初始化 a[0] = 1, a[1] = 3, ...
1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0
};
// 对于 MAX=6, 数组保有 1,8,6,4,2,0
// 对于 MAX=13,数组保有 1,3,5,7,9,0,0,0,8,6,4,2,0(“稀疏数组”)
|
(C99 起) |
在初始化未知大小的数组时,指派初始化器的最大下标会确定所声明的数组大小。
嵌套数组
若数组的元素是数组、结构体或联合体,则对应的花括号环绕的初始化器列表中的初始化器是任何对于那些成员合法的初始化器,除了它们的花括号可以按如下方式省略:
若嵌套初始化器从左花括号开始,则整个直到其右花括号为止的初始化器初始化对应的数组元素:
int y[4][3] = { // 4 个 3 个 int 的数组的数组(4*3 矩阵)
{ 1 }, // 0 行初始化到 {1, 0, 0}
{ 0, 1 }, // 1 行初始化到 {0, 1, 0}
{ [2]=1 }, // 2 行初始化到 {0, 0, 1}
}; // 3 行初始化到 {0, 0, 0}
若嵌套初始化器不从左花括号开始,则只有的来自列表的充足的初始化器会被用于说明子数组、结构体或联合体;任何剩下的初始化器都被留作初始化下一个数组元素:
int y[4][3] = { // 4 个 3 个 int 的数组的数组(4*3 矩阵)
1, 3, 5, 2, 4, 6, 3, 5, 7 // 0 行初始化到 {1, 3, 5}
}; // 1 行初始化到 {2, 4, 6}
// 2 行初始化到 {3, 5, 7}
// 3 行初始化到 {0, 0, 0}
struct { int a[3], b; } w[] = { { 1 }, 2 }; // 结构体的数组
// { 1 } 被视为完整花括号的初始化器,用于数组的 #0 元素
// 该元素初始化为 { {1, 0, 0}, 0}
// 2 被视为数组的 #1 元素的首个初始化
// 该元素初始化为 { {2, 0, 0}, 0}
|
数组指代器可以嵌套;对于嵌套数组的带方括号常量表达式跟在对于外层数组的带方括号常量表达式之后: int y[4][3] = {[0][0]=1, [1][1]=1, [2][0]=1}; // 0 行初始化为 {1, 0, 0}
// 1 行初始化为 {0, 1, 0}
// 2 行初始化为 {1, 0, 0}
// 3 行初始化为 {0, 0, 0}
|
(C99 起) |
注解
数组初始化器中的子表达式求值顺序在 C 中是不定序的(但在从 C++11 开始的 C++ 中不是):
int n = 1;
int a[2] = {n++, n++}; // 未指明,但是是良定义行为,
// n 自增二次(以任意顺序)
// a 初始化为 {1, 2} 和为 {2, 1} 均合法
puts((char[4]){'0'+n} + n++); // 未定义行为:
// n 的自增和读取是无序的
|
在 C 中,初始化器的花括号列表不能为空。 C++ 允许空列表: |
(C23 前) |
|
空初始化器能用于初始化数组: |
(C23 起) |
int a[3] = {0}; // C 与 C++ 中均为清零块作用域数组的合法途径
int a[3] = {}; // C++ 中合法的清零块作用域数组的途径; C23 起合法
与所有其他初始化一样,在初始化静态或线程局域存储期的数组时,初始化器列表中的每个表达式都必须是常量表达式:
static char* p[2] = {malloc(1), malloc(2)}; // 错误
示例
int main(void)
{
// 下列四个数组的声明是相同的
short q1[4][3][2] = {
{ 1 },
{ 2, 3 },
{ 4, 5, 6 }
};
short q2[4][3][2] = {1, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 4, 5, 6};
short q3[4][3][2] = {
{
{ 1 },
},
{
{ 2, 3 },
},
{
{ 4, 5 },
{ 6 },
}
};
short q4[4][3][2] = {1, [1]=2, 3, [2]=4, 5, 6};
// 下标能与枚举常量关联
// 使用带指派符的数组:
enum { RED, GREEN, BLUE };
const char *nm[] = {
[RED] = "red",
[GREEN] = "green",
[BLUE] = "blue",
};
}
引用
- C17 标准(ISO/IEC 9899:2018):
- 6.7.9/12-39 Initialization (第 101-105 页)
- C11 标准(ISO/IEC 9899:2011):
- 6.7.9/12-38 Initialization (第 140-144 页)
- C99 标准(ISO/IEC 9899:1999):
- 6.7.8/12-38 Initialization (第 126-130 页)
- C89/C90 标准(ISO/IEC 9899:1990):
- 6.5.7 Initialization