在定义数组之后,使用到数组变量时,会获取首元素的地址,例如在下面的程序中将指出,数组arr
与&arr[0]
的值是相同的:
#include <stdio.h>
int main(void) {
int arr[10] = {0};
printf("arr :\t\t%p\n", arr);
printf("&arr[0] :\t%p\n", &arr[0]);
return 0;
}
执行结果:
arr : 0061FEA8
&arr[0] : 0061FEA8
之前也曾经谈过,数组索引其实是相对于首元素地址的位移量,下面这个程序以指针运算与数组索引操作,显示出相同的对应地址值:
#include <stdio.h>
#define LEN 10
int main(void) {
int arr[LEN] = {0};
int *p = arr;
for(int i = 0; i < LEN; i++) {
printf("&arr[%d]: %p", i ,&arr[i]);
printf("\t\tptr + %d: %p\n", i, p + i);
}
return 0;
}
每个元素的地址类型是int*
,执行结果如下:
&arr[0]: 0061FEA0 ptr + 0: 0061FEA0
&arr[1]: 0061FEA4 ptr + 1: 0061FEA4
&arr[2]: 0061FEA8 ptr + 2: 0061FEA8
&arr[3]: 0061FEAC ptr + 3: 0061FEAC
&arr[4]: 0061FEB0 ptr + 4: 0061FEB0
&arr[5]: 0061FEB4 ptr + 5: 0061FEB4
&arr[6]: 0061FEB8 ptr + 6: 0061FEB8
&arr[7]: 0061FEBC ptr + 7: 0061FEBC
&arr[8]: 0061FEC0 ptr + 8: 0061FEC0
&arr[9]: 0061FEC4 ptr + 9: 0061FEC4
在这个程序中,将数组的首元素地址指定给p
,然后对p
递增运算,每递增一个单位,数组相对应索引的元素之地址都相同。
也可以利用指针运算来取出数组的元素值,如以下的程序所示:
#include <stdio.h>
#define LEN 5
int main(void) {
int arr[LEN] = {10, 20, 30, 40, 50};
int *p = arr;
// 以指针方式访问
for(int i = 0; i < LEN; i++) {
printf("*(p + %d): %d\n", i , *(p + i));
}
putchar('\n');
// 以指针方式访问数据
for(int i = 0; i < LEN; i++) {
printf("*(arr + %d): %d\n", i , *(arr + i));
}
return 0;
}
执行结果:
*(p + 0): 10
*(p + 1): 20
*(p + 2): 30
*(p + 3): 40
*(p + 4): 50
*(arr + 0): 10
*(arr + 1): 20
*(arr + 2): 30
*(arr + 3): 40
*(arr + 4): 50
在上面的程序中,可以使用指针运算配合*
运算符来取出数组中的每个元素,也可以配合下标运算符来取出数组元素。
在〈数组〉中谈过,可以使用sizeof
来计算数组长度,在认识指针及其运算后,透过以下也可以计算出数组长度:
int arr[] = {10, 20, 30, 40, 50};
int len = *(&arr + 1) - arr;
来解释一下为什么这行得通,如果使用&arr
会获取arr
变量的地址值,也就是数组数据存储的地址,与首元素地址是相同的值:
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
printf("%p\n", arr); // 显示 0061FEBC
printf("%p\n", &arr); // 显示 0061FEBC
return 0;
}
每个数组元素的地址类型是int*
,这表示对它进行运算时,是以int
长度为单位,而arr
变量的地址处就是数组数据的开端,&arr
类型会是…呃…int (*)[5]
,5 是数组长度,如果想定义相对应的变量,可以如下:
int (*p)[5] = &arr;
int (*)[5]
表示,对它进行运算时,是以 5 个int
长度为单位,因此&arr + 1
的结果,会是数组使用的空间后之地址,而*(&arr + 1)
的值类型会回到int*
,也就是最后一个元素后之地址,这时就可以与int*
的arr
进行相减,也就是与第一个元素之地址相减,就可以得到数组长度了。
举这个例子的重点之一是,对于同一个地址,指针的类型决定了该怎么看得相对应相加、相减计算;另一个重点是,透过数组变量会获取首元素的地址,将数组变量指定给指针p
,就只是获取首元素地址并存储在p
,如果将p
传给sizeof
,那使用的会是指针p
的类型,而不是原数组的类型,这会令sizeof
、以及方才那神奇计算长度的方式失效,例如:
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%p\n", p); // 显示 0061FEBC
printf("%p\n", &p); // 显示 0061FEB8
printf("%d\n", sizeof(p)/sizeof(*p)); // 显示 1
printf("%d\n", *(&p + 1) - p); // 显示 -1605549
return 0;
}
C++ 11 提供了begin
与end
函数,可以计算数组长度:
constexpr int LENGTH = 5;
int arr[LENGTH] = {10, 20, 30, 40, 50};
cout << end(arr) - begin(arr) << endl; // 显示 5
在 C 语言中,可以这么做:
int arr[] = {10, 20, 30, 40, 50};
int *begin = arr;
int *end = *(&arr + 1);
printf("%d\n", end - begin);
因此基于指针,也可以使用以下的风格来迭代数组,而不是使用索引:
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *begin = arr;
int *end = *(&arr + 1);
for(int *it = begin; it < end; it++) {
printf("%d ", *it);
}
return 0;
}
在〈二维(多维)数组〉中谈过,C 没有二维数组这种东西,二维或多维数组的概念,是以数组的数组(arrays of arrays)来实现,例如,底下可以分别求得maze
的列数与每列的长度:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
printf("ROWS: %d\n", sizeof(maze) / sizeof(maze[0]));
printf("LEN: %d\n", sizeof(maze[0]) / sizeof(maze[0][0]));
return 0;
}
或者是使用以下程序:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
printf("ROWS: %d\n", *(&maze + 1) - maze);
printf("LEN: %d\n", *(&maze[0] + 1) - maze[0]);
return 0;
}
执行结果都是:
ROWS: 2
LEN: 3
〈二维(多维)数组〉也曾经举了个例子:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
for(int i = 0; i < ROWS; i++) {
int *row = maze[i];
for(int j = 0; j < LEN; j++) {
printf("%d\t", row[j]);
}
printf("\n");
}
}
现在已经认识指针了,上例中的maze[i]
获取其实是每列一维数组的首元素地址,然而指定给int*
的row
的话,如稍早谈到的,row
就只会存储地址,也就是row
并没有每列一维数组的长度信息。
虽说如此,对多数情境来说,这种从二维数组中获取每列的方式已经足够,类似地,若不管长度信息会失去的问题,也可以如下模拟二维数组:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int row1[LEN] = {1, 2, 3};
int row2[LEN] = {4, 5, 6};
int* maze[ROWS] = {row1, row2};
for(int i = 0; i < ROWS; i++) {
int *row = maze[i];
for(int j = 0; j < LEN; j++) {
printf("%d\t", row[j]);
}
printf("\n");
}
return 0;
}
说是模拟的原因在于,maze
实际上是int*
的一维数组,maze[0]
、maze[1]
仅存储row1
、row2
首元素的地址,并没有row1
、row2
的长度信息,虽说如此,对大多数情境来说,想用一维数组组合出二维数组,以上的方式也已经足够。
接下来纯粹是挑战,可以自行研究一下,就不多做说明了。以下程序示范了如何获取二维数组中的每一列,并保留长度信息:
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
for(int i = 0; i < ROWS; i++) {
int (*row)[LEN] = &maze[i];
for(int j = 0; j < LEN; j++) {
printf("%d\t", (*row)[j]);
}
printf("\n");
}
}
有没有办法完全基于指针来迭代数组,而不是依靠索引呢?
#include <stdio.h>
#define ROWS 2
#define LEN 3
int main(void) {
int maze[ROWS][LEN] = {
{1, 2, 3},
{4, 5, 6}
};
int(*mazeBegin)[LEN] = maze;
int(*mazeEnd)[LEN] = *(&maze + 1);
for(int(*row)[LEN] = mazeBegin; row < mazeEnd; row++) {
int *begin = *row;
int *end = *(row + 1);
for(int* it = begin; it < end; it++) {
printf("%d\t", *it);
}
printf("\n");
}
return 0;
}
底下的写法,maze[0]
、maze[1]
会失去row1
、row2
的长度信息:
int row1[LEN] = {1, 2, 3};
int row2[LEN] = {4, 5, 6};
int* maze[ROWS] = {row1, row2};
有没有办法不失去长度信息呢?
#include <stdio.h>
#define ROWS 2
#define LEN 3
typedef int(*Row)[LEN];
int main(void) {
int row1[LEN] = {1, 2, 3};
int row2[LEN] = {4, 5, 6};
Row maze[ROWS] = {&row1, &row2};
int rows = *(&maze + 1) - maze;
for(int i = 0; i < rows; i++) {
Row row = maze[i];
int len = *(row + 1) - *row;
for(int j = 0; j < len; j++) {
printf("%d\t", *(*row + j));
}
printf("\n");
}
return 0;
}
typedef
可用来为指定的类型取别名,就上例来说,为int(*)[LEN]
取了个别名ROW
,这样比较便于使用ROW
来定义,当然,这些挑战的写法不容易理解,纯粹就是探讨,程序基本上还是选择各自情境下易懂的写法会比较好。