指定初始化
C语言中具有自动存储期限(automatic storage duration)的对象如果没有被显式初始化,其值是不确定的;具有静态存储期限(static storage duration)或者线程存储期限(thread storage duration)的对象如果没有被显式初始化,将会:
- 如果对象类型为指针类型,将初始化为空指针;
- 如果对象类型为算术类型,将初始化为0;
- 如果对象类型为聚合类型(aggregate type),所有成员将根据上述规则初始化,并且所有填充将初始化为0位;
- 如果对象类型为联合类型,第一个命名成员将根据上述规则初始化,并且所有填充将初始化为0位。
标量类型对象的初始化式应为单一表达式,例如:
int a = 5; double b = {3.14};
其中{ }是可选的;但对于聚合类型或者联合类型对象,{ }是必须的(注:如果使用字符串或者宽字符串对字符型数组初始化,{ }是可选的。)。对象的初始值是转换后的表达式的值;初始化过程中对类型的限制以及转换规则与简单赋值运算相同,例如:
int a = 3.14; //a的初始值为3。
指定初始化(specified initialization)就是对指定的元素或者成员进行初始化,而其它元素或者成员进行默认初始化。C语言中指定初始化的指示符有两种形式:
第一种形式:
[ constant-expression ]
第二种形式:
.identifier
第一种形式用于数组类型,constant-expression应为整数常量表达式;如果是未知大小的数组,任何非负值都是有效的。
int n = 3; int a[] = {[3]=5}; int b[] = {[n]=5}; //非法。
数组a是一个有4个数组元素的数组,其中a[3]的初始值为5,其它元素的初始值为0。对于数组b,其初始化是非法的,因为n不是整数常量,是整数变量。
第二种形式用于结构或者联合类型,identifier应为结构或者联合的成员名。
struct data{ int i; double d; } number = {.i=5};
上例中number.i的初始值为5,number.d的初始值为0.0。
下面按数据类型讨论指定初始化的应用,涉及的数据类型包括:数组类型、结构类型、联合类型。
① 数组类型
数组初始化时如果初始化列表项的数目小于数组元素数目,未显式初始化的数组元素将像静态存储期限对象一样隐式初始化。数组指定初始化时可以对单个数组元素指定初始化,也可以对多个数组元素指定初始化。
int arrOne[5] = {[3]=5}; int arrTwo[5] = {[1]=3, [3]=5};
指定初始化可以与常用初始化混合使用。
int arr[] = {15, [2]=7, 9}; //arr[0]初始值为15,arr[2]初始值为7,arr[3]初始值为9。
在混合初始化中,指定初始化之前的初始化按数组中元素递增的顺序从数组第一个元素开始初始化(即从arr[0]开始初始化。);指定初始化对指定的元素进行初始化(即对arr[2]初始化。);指定初始化之后的初始化将从指定元素后第一个元素开始按元素递增的顺序初始化(即从arr[3]开始初始化。)。
如果同时对多个元素指定初始化,指定初始化的先后顺序对指定的元素没有影响,但可能会影响其它元素的值。
int arrOne[5] = {[1]=6, 7, [3]=8, 9}; int arrTwo[5] = {[3]=8, 7, [1]=6, 9}; for(int i=0; i<5; ++i) { printf("arrOne[%d]=%d ", i, arrOne[i]); printf("arrTwo[%d]=%d\n", i, arrTwo[i]); }
将输出:
arrOne[0]=0 arrTwo[0]=0
arrOne[1]=6 arrTwo[1]=6
arrOne[2]=7 arrTwo[2]=9
arrOne[3]=8 arrTwo[3]=8
arrOne[4]=9 arrTwo[4]=7
对于数组arrOne,表达式7初始化的是数组元素arrOne[2],表达式9初始化的是数组元素arrOne[4];对于数组arrTwo,表达式7初始化的是数组元素arrTwo[4],表达式9初始化的是数组元素arrTwo[2]。
使用混合形式初始化数组元素时,部分元素的初始值可能会被后面的初始化值覆盖。
int arr[] = {1, 2, 3, 4, [2]=10};
数组元素arr[2]的初始值先为3,然后指定初始化又将其值修改为10,最终数组元素arr[2]的初始值为10。对于这种现象不同编译器的表现是不一样的,其中Pelles C编译器会给出Multiple initializers for the same element(at offset 8).的警告。
如果数组元素数目是已知的,假设数组元素数为N,指示符中constant-expression取值范围应在0到N-1之间(包括0和N-1)。
int arr[5] = {[5]=10}; //非法。
因为指示符中的数字超出了数组边界。
如果数组元素数目是未知的,指示符中constant-expression值可以是任何非负值;编译器将根据数组元素的最大下标值确定数组元素数目。
int arr[] = {[15]=6, 7, 8};
指示符中的数字为15,后面还有2个初始值,所以最大的数组下标为17,数组元素数目为18。
不能对变长数组进行指定初始化。
int n = 5; int arr[n] = {[3]=9}; //非法。
变长数组的大小不是在程序编译时确定的,而是在程序执行时确定的,编译时编译器会给出类似error: array index in initializer exceeds array bounds的出错信息。
初始化时每个大括号括起来的初始化列表都有一个关联的当前对象(associated current object)。如果不存在指定初始化,当前对象的子对象将按顺序进行初始化。对于数组,数组元素将按下标递增的顺序初始化。如果存在指定初始化,将初始化指示符指定的子对象,然后从该子对象后的第一个子对象开始按顺序初始化。
int arrOne[2][3] = {1, 2, 3}; int arrTwo[2][3] = {{1},{2, 3}};
对于第一个语句,{ }关联的当前对象为数组arrOne,当前对象的子对象为数组元素,所以第一个语句就是对arrOne中的数组元素初始化。对于第二个语句,内层第一个{ }关联的当前对象为数组arrTwo的第一行,子对象为第一行中的数组元素,该{ }中的初始化列表项用于初始化数组arrTwo的第一行数组元素;内层第二个{ }情况和内层第一个{ }基本相似,不同的是其关联的当前对象为数组arrTwo的第二行。
下面两种形式的指定初始化结果是相同的,都是将数组元素arr[1][2]初始化为5。
int arr[2][3] = {[1][2]=5};
int arr[2][3] = {{ }, {[2]=5}};
通常情况下,初始化时最大限度的使用大括号和最低限度的使用大括号是不容易引起混淆的两种方式。
② 结构类型
结构初始化有两种形式,一种使用初始化列表;另一种使用兼容类型的单一表达式,例如:
/*使用初始化列表初始化结构。*/ struct data { int i; double d; } number = {5, 3.14}; /*使用单一表达式初始化结构。*/ struct data myNumber = number;
本文对结构初始化的讨论仅涉及第一种情况。
结构初始化时如果初始化列表项的数目小于结构成员数目,未显式初始化的结构成员将像静态存储期限对象一样隐式初始化。
struct data { int i; double d; } number = {.d=3.14};
初始化后结构成员number.i的初始值为0,结构成员number.d的初始值为3.14。
与数组一样,也可以对结构进行混合初始化。混合初始化时,指定初始化之前的初始化按结构中成员顺序从结构第一个成员开始初始化;指定初始化对指定的结构成员进行初始化;指定初始化之后的初始化将从指定成员后第一个成员开始按成员顺序初始化。如果同时对多个成员指定初始化,指定初始化的先后顺序对指定的成员没有影响,但可能会影响其它成员的值。
struct data{ int a; unsigned b; int c; unsigned d; }; struct data one = {.a=5, 7, .c=9, 8}; struct data two = {.c=9, 7, .a=5, 8}; printf("one.a=%d two.a=%d\n", one.a, two.a); printf("one.b=%u two.b=%u\n", one.b, two.b); printf("one.c=%d two.c=%d\n", one.c, two.c); printf("one.d=%u two.d=%u\n", one.d, two.d);
将输出:
one.a=5 two.a=5
one.b=7 two.b=8
one.c=9 two.c=9
one.d=8 two.d=7
与数组一样,使用混合形式初始化结构成员时,部分成员的初始值可能会被后面的初始化值覆盖。结构初始化时,{ }的使用与数组也基本相似,例如:
struct data{ int a[3]; int b[3]; }; struct data one = {1, 2, 3}; struct data two = {{1}, {[1]=2, 3}}; for(int i=0; i<3; ++i) { printf("one.a[%d]=%d two.a[%d]=%d\n", i, one.a[i], i, two.a[i]); } for(int i=0; i<3; ++i) { printf("one.b[%d]=%d two.b[%d]=%d\n", i, one.b[i], i, two.b[i]); }
将输出:
one.a[0]=1 two.a[0]=1
one.a[1]=2 two.a[0]=0
one.a[2]=3 two.a[0]=0
one.b[0]=0 two.b[0]=0
one.b[1]=0 two.b[1]=2
one.b[2]=0 two.b[2]=3
③ 联合类型
联合初始化有两种形式,一种使用初始化列表;另一种使用兼容类型的单一表达式,例如:
/*使用初始化列表初始化联合。*/ union data { int i; double d; } number = {5}; /*使用单一表达式初始化联合。*/ union data myNumber = number;
本文对联合初始化的讨论仅涉及第一种情况。
联合初始化时默认情况下初始化的是第一个成员;如果要对特定成员初始化,可以使用指定初始化。
union data{ int i; double d; }; union data one = {5}; union data two = {.d=3.14};
上述两个语句分别将one.i初始化为5, two.d初始化为3.14。
使用初始化列表初始化包含联合的对象时,初始化一个联合成员后,下一个初始化的并不是联合的下一个成员;而是包含联合的对象的下一个子对象。
struct data{ union{ int i; double d; } number; int arr[3]; }; struct data myData = {2, 3};
表达式2初始化的是成员myData.number.i;表达式3初始化的是成员myData.arr[0],而不是成员myData.number.d。上述初始化语句如果改写成以下形式,意思可能会更清楚一点。
struct data myData = {{2}, {3}};
联合初始化时{ }的使用可参阅数组或者结构的相关部分,这里就不再重复了。