本章我们还要再看几个与数组有关的杂项内容。
static 关键字它们并不十分常见,但既然属于较新的规范,我们还是来瞥一眼。
如果还记得前面的内容,那么在函数形参列表中,下面两种写法是等价的:
int func(int *p) {...}
int func(int p[]) {...}你可能也还记得,可以像这样给指针变量加上类型限定符:
int *const p;
int *volatile p;
int *const volatile p;
// 等等但如果在形参列表里用的是数组写法,又该怎么做呢?
答案是写在方括号里,后面还可以跟上可选的元素个数。下面两行是等价的:
int func(int *const volatile p) {...}
int func(int p[const volatile]) {...}
int func(int p[const volatile 10]) {...}如果是多维数组,类型限定符要放在第一对方括号中。
static类似地,也可以在形参列表的数组中使用 static 关键字。
这东西我在实际代码里从没见过。它总是后面跟着一个维度:
int func(int p[static 4]) {...}在上面的例子中,这意味着编译器会假定你传给该函数的数组至少有 4 个元素。
其他情况都属于未定义行为。
int func(int p[static 4]) {...}
int main(void)
{
int a[] = {11, 22, 33, 44};
int b[] = {11, 22, 33, 44, 55};
int c[] = {11, 22};
func(a); // 没问题!a 有 4 个元素,正好是最小值
func(b); // 没问题!b 至少有 4 个元素
func(c); // 未定义行为!c 不足 4 个元素!
}这基本上设定了数组允许的最小尺寸。
重要提示:编译器并不会禁止你传入更小的数组。它多半不会警告你,运行时也检测不出来。
把 static 写在那里,你等于在说:“我以双重机密郑重保证,绝不会传入比这更小的数组。”而编译器则说:“行吧。”然后信任你不会那么做。
这样编译器就能放心地做某些代码优化,因为它知道你这个程序员总会做出正确的选择。
在数组初始化器方面,C 可以说有点灵活。
我们已经见过一些了:缺失的值都会被替换为零。
例如,下面这样可以把一个 5 元素数组初始化为 1,2,0,0,0:
int a[5] = {1, 2};或者用下面的写法把整个数组设为零:
int a[5] = {0};但初始化多维数组时就有意思了。
我们来创建一个 3 行 2 列的数组:
int a[3][2];写点代码初始化它,并打印结果:
#include <stdio.h>
int main(void)
{
int a[3][2] = {
{1, 2},
{3, 4},
{5, 6}
};
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 2; col++)
printf("%d ", a[row][col]);
printf("\n");
}
}运行后得到符合预期的结果:
1 2
3 4
5 6我们省掉初始化器里的几个元素,看看它们会不会被设为零:
int a[3][2] = {
{1, 2},
{3}, // 把 4 省掉了!
{5, 6}
};输出为:
1 2
3 0
5 6现在我们干脆把中间那一整项也去掉:
int a[3][2] = {
{1, 2},
// {3, 4}, // 把这一整项直接删掉
{5, 6}
};于是得到下面这样,可能和你想的不一样:
1 2
5 6
0 0但停下来想想就明白了:我们只提供了够两行用的初始化器,所以它们被用在了前两行上,其余元素则被初始化为零。
到目前为止一切正常。一般来说,如果初始化器少写了一部分,编译器会把对应的元素设为 0。
但咱们来点疯狂的。
int a[3][2] = { 1, 2, 3, 4, 5, 6 };什么——?这是个二维数组,可初始化器只有一维!
结果这居然合法(不过打开相应警告后,GCC 会对此发出警告)。
它的基本行为是从左到右依次填充第 0 行、第 1 行、第 2 行的元素。
所以打印时,输出顺序是:
1 2
3 4
5 6如果省掉几个:
int a[3][2] = { 1, 2, 3 };它们会被 0 填满:
1 2
3 0
0 0所以如果想把整个数组填满 0,尽管这样写:
int a[3][2] = {0};但我的建议是:既然是二维数组,就用二维初始化器,这样代码更易读。(把整个数组初始化为 0 的情况除外,此时无论数组是几维,惯用写法都是 {0}。)