文章目录
数组和指针
10.1数组
10.1.2指定初始化器(C99)
C99增加了一个新特性:指定初始化器。利用该特性可以初始化指定的数组元素。
例如,只初始化数组中的最后一个元素:
int arr[6] = {[5] = 212}; // 把arr[5]初始化为212
其他元素都会被设置为0。另一方面:
#include <stdio.h>
#define MONTHS 12
int main(void) {
int days[MONTHS] = {31, 28, [4] = 31, 30, 31, [1] = 29};
int i;
for (i = 0; i < MONTHS; i++) {
printf("%2d %d\n", i + 1, days[i]);
}
return 0;
}
/*output:
1 31
2 29
3 0
4 0
5 31
6 30
7 31
8 0
9 0
10 0
11 0
12 0
*/
指定初始化器有两个重要的特性:
- 如果指定初始化器后面有更多的值,那么后面这些值将被用于初始化指定元素后面的元素。也就是说,在
days[4]
被初始化为31后,days[5]
和days[6]
将分别被初始化为30和31。- 如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化。
10.1.5指定数组的大小
在C99标准之前,声明数组时只能在方括号中使用整型常量表达式,即由整型常量构成的表达式。
sizeof
表达式被视为整型常量,但是(与c++不同)const
值不是。另外,表达式的值必须大于0。
另一方面:
int n = 5;
int m = 8;
float a8[n]; // C99之前不允许
float a9[m]; // C99之前不允许
C99标准允许变长数组或简称VLA的存在(C11放弃了这一创新的举措,把VLA设定为可选,而不是语言必备的特性)。
10.2多维数组
10.2.2其他多维数组
可以把一维数组想象成一行数据,把二维数组想象成数据表,把三维数组想象成一叠数据表。
10.3指针和数组
数组名是数组首元素的地址。在c中,指针加1指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道储存对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则
*pt
就无法正确地取回地址上的值)。
实际上,c语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]
的意思是*(ar + n)
。可以认为*(ar + n)
的意思是:到内存的ar
位置,然后移动n
个单元,检索储存在那里的值。
10.4函数、数组和指针
int func(int ar[], int n);
其中,
ar
并不是数组本身,它是一个指向数组首元素的指针。因此,其大小是指针的大小。
10.7指针和多维数组
处理多维数组的函数要用到指针,所以在使用这种函数之前,先要更深入地学习指针。例如,
int arr[4][2]
,数组名arr
是该数组首元素的地址。在本例中,arr
的首元素是一个内含两个int
值的数组,所以arr
是这个内含两个int
值的数组的地址。下面,从指针的属性进一步分析:
- 因为
arr
是数组首元素的地址,所以arr
的值和&arr[0]
的值相同。而arr[0]
本身是一个内含两个整数的数组,所以arr[0]
的值和它的首元素(一个整数)的地址(即&arr[0][0]
的值)相同。简而言之,arr[0]
是一个占用一个int
大小对象的地址,而arr
是一个占用两个int
大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以arr
和arr[0]
的值相同。- 给指针或地址加1,其值会增加对应类型大小的数值。在这方面,
arr
和arr[0]
不同,因为arr
指向的对象占用了两个int
大小,而arr[0]
指向的对象只占用一个int
大小。因此,arr + 1
和arr[0] + 1
的值不同。- 解引用一个指针或在数组名后使用
[]
运算符,得到引用对象代表的值。因为arr[0]
是该数组首元素(arr[0][0]
)的地址,所以*(arr[0])
表示储存在arr[0][0]
上的值(即一个int
类型的值)。与此类似,*arr
代表该数组首元素(arr[0]
)的值,但是arr[0]
本身是一个int
类型值的地址。该值的地址是&arr[0][0]
,所以*arr
就是&arr[0][0]
。对两个表达式应用解引用运算符表明,**arr
与*&arr[0][0]
等价,这相当于arr[0][0]
,即一个int
类型的值。简而言之,arr
是地址的地址,必须解引用两次才能获得原始值。
#include <stdio.h>
int main(void) {
int arr[4][2] = {
{2, 4},
{6, 8},
{1, 3},
{5, 7}
};
printf(" arr = %p, arr + 1 = %p\n",
arr, arr + 1);
printf("arr[0] = %p, arr[0] + 1 = %p\n",
arr[0], arr[0] + 1);
printf(" *arr = %p, *arr + 1 = %p\n",
*arr, *arr + 1);
printf("arr[0][0] = %d\n", arr[0][0]);
printf(" *arr[0] = %d\n", *arr[0]);
printf(" **arr = %d\n", **arr);
printf(" arr[2][1] = %d\n", arr[2][1]);
printf("*(*(arr+2) + 1) = %d\n", *(*(arr + 2) + 1));
return 0;
}
/*output:
arr = 005DF9E8, arr + 1 = 005DF9F0
arr[0] = 005DF9E8, arr[0] + 1 = 005DF9EC
*arr = 005DF9E8, *arr + 1 = 005DF9EC
arr[0][0] = 2
*arr[0] = 2
**arr = 2
arr[2][1] = 3
*(*(arr+2) + 1) = 3
*/
10.7.3函数和多维数组
注意,下面的声明不正确:
int func(int ar[][], int rows);
由于编译器会把数组表示法转换成指针表示法。例如,编译器会把
ar[1]
转换成ar + 1
。编译器对ar + 1
求值,要知道ar
锁指向的对象大小。下面的声明:
int func(int ar[][4], int rows);
表示
ar
指向一个内含4个int
类型值的数组,所以ar + 1
的意思是,该地址加上16个字节。如果第2对方括号是空的,编译器就不知道该怎样处理。
也可以在第1对方括号中写上大小,但是编译器会忽略该值。一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:
int func(int ar[][12][20][30], int rows);
因为第1对方括号只用于声明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。下面的声明与该声明等价:
int func(int (*ar)[12][20][30], int rows);
10.8变长数组(VLA)
C99新增了变长数组(VLA),允许使用变量表示数组的维度,但是有一些限制,即变长数组必须是自动存储类别,这意味着无论在函数中声明还是作为函数形参声明,都不能使用
static
或extern
存储类别说明符。而且,不能在声明中初始化它们。最终,C11把变长数组作为一个可选特性,而不是必须强制实现的特性。
注意,变长数组不能改变大小,其中的变不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。这里的变指的是:在创建数组时,可以使用变量指定数组的维度。
10.9复合字面量
对于数组,复合字面量类似数组初始化列表,前面是用括号括起来的类型名:
// 匿名数组,int[2]即是复合字面量的类型名。
(int[2]) {10, 20};