数组表示多个相同类型的元素的有序集合,数组在内存中是连续分布的,因此,使用指针运算读取元素非常快。
声明数组需要定义元素的类型,然后将数组名称后面加上中括号,中括号里面可以写上数组的最大长度,也可以不写,不过,最佳实践是永远都写上最大长度。等号右边将元素用花括号包裹,逗号隔开:
int arr[3] = {1,2,3};
实际元素的数量可以少于定义的长度,但是不能超过定义的长度。
数组名称是一个指针类型,指向数组的第一个元素的地址,也叫数组的首地址:
int arr[3] = {1,2,3};
printf("first element address: %p \n", &arr[0]);
printf("%p", arr);
通过输出的结果可以看到,arr的值是一个地址,与数组首地址&arr[0]是相同的。
数组名是一个指针,如果该指针加1,表示该指针向后移动一个元素类型所占字节的长度,也就是跳到下一个元素,例如本例中,元素类型是int,占4个字节,所以向后移动了4个字节,刚好跳到了下一个元素的位置。
在下述代码中,定义了一个指针p,并将数组名称赋给它。此时p指向第一个元素,之后p++,此时p跳到了下一个元素。分别用p和*p打印除了此时的地址及值:
int arr[3] = {1,2,3};
int * p = arr;
printf("first address: %p \n", p);
printf("first address: %p \n", &arr[0]);
printf("first value: %d \n", *p);
printf("first value: %d \n", arr[0]);
p++;
printf("second address: %p \n", p);
printf("second address: %p \n", &arr[1]);
printf("second value: %d \n", *p);
printf("second value: %d \n", arr[1]);
p++;
printf("thrid address: %p \n", p);
printf("thrid address: %p \n", &arr[2]);
printf("third value: %d \n", *p);
printf("third value: %d \n", arr[2]);
输出结果如下:
first address: 0x7ffdb1610e80
first address: 0x7ffdb1610e80
first value: 1
first value: 1
second address: 0x7ffdb1610e84
second address: 0x7ffdb1610e84
second value: 2
second value: 2
thrid address: 0x7ffdb1610e88
thrid address: 0x7ffdb1610e88
third value: 3
third value: 3
根据结果可以得知,每次指针加1,地址就加了4个字节,刚好是整数类型所需的字节数。同时,使用*p读取元素值,和使用arr[1]读取元素值是等价的。
请注意,指针p并不知道自己指向的是数组的某个元素,还是一个独立的整数,它只知道自己指向了一个存储整型数据的内存地址而已。运行p++,它也只是按照数据类型移动了4个字节而已,它也并不知道移到了下一个元素,只是因为数组在内存中是连续分布的,指针的移动“刚好对上了”下一个元素的地址而已。总而言之,指针不认识数组,只知道自己指向一个地址,然后根据指针运算规则移动一定的字节长度,然后取出值而已。
我们可以接着上面的代码,虽然上面已经移动到了最后一个元素的位置,但是仍然可以再继续移动:
p++;
printf("next address: %p \n", p);
printf("next value: %d \n", *p);
输出结果是:
thrid address: 0x7ffdb1610e8c
third value: -1025609304
可以看到,指针依然移动了4个字节,依然读取了内存中的值,但这个值是没有意义的,它也不知道自己是否还处于数组所在的内存区域中。