13、指针与多维数组
1、数组排列
二维数组可看作一维数组的堆叠,如图所示。每个一维数组在内存中都是线性排列的。
如图所示,首先排第0行,接着排第一行,以此类推。
下面个代码演示了如何访问二维数组。代码中使用<变量名>[行][列](data[i][j])表达式。
#include <stdio.h> #include <string.h> int main() { int data[5][5]; int i,j; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { data[i][j] = -1; } } }
2、多维数组指针
(1)一维数组语法
数组变量名
假定一个int型一维数组名为arr[10];指定的数组变量名等价于第0个元素的地址。
arr=starting of the 0th element
数组运算
<数组变量名>+偏移量
该表达式的结果为与第0个元素偏移某个距离后对应元素的地址。
下标符号的概念
arr[i]==i[arr]==*(arr+i)
(2)二维数组语法
数组变量名
假定int型变量arr[5][5],则变量名arr为数组变量名。对于二位数组,制定数组名时会创建第0行的地址(一维数组),接着变量名加偏移量得到第i行的起始地址。可以看到下面代码段会得到同样的结果。
#include <stdio.h> #include <string.h> int main() { int data[5][5]; int i,j; int count = 0; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { data[i][j] =count++ ; } } printf("数组开始的地址为%p\n", data); for (i = 0; i < 5; i++) { printf("%dth row location =%p\n", i, data[i]); printf("Loc %d,1=%p\n ", i,&data[i][0]); } return 0; }
运行结果为:
上面代码中,通过变量名和第一个索引(data[i])访问每一行(本身为一维数组)。
因此,表达式<变量名>+索引==<变量名>[索引]将得到第i行的起始地址。
实例:
arr+i==arr[i]
数组地址运算
对于表达式<变量名>[i]+偏移量,偏移量与数组元素类型一致。由于这里是一个整型数组,因此加4个字节到数组基地址,最后跳到同一行的第2列的位置。下面通过示例对上述表达式访问各行的每一列进行进一步理解。
#include <stdio.h> #include <string.h> int main() { int data[5][5]; int i,j; int count = 0; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { data[i][j] =count++ ; } } for (i = 0; i < 5; i++) { printf("%dth row location =%p\n", i, data[i]); printf("columns\n"); for (j = 0; j < 5; j++) { printf("%d=%p ", j, data[i]+j); } printf("\n"); } return 0; }
运行结果如下:
位置的值
我们通过下述表示式访问二维数组第i行第j列的值:
<数组变量名>[行][列]
其中行和列为下标
例如
arr[i][j];
当然也可以用指针符号指定它。因为arr[i]==第i行的地址,如果将索引j加给它就会调到改行第j列(arr[i]+j)。因此可用&arr[i][j]或(arr[i]+j)得到arr[i][j]元素的地址。
我们使用上面两个表达式的“取值”操作符得到第i行第j列的值。例如*(&arr[i][j])或*(arr[i]+j)。所以我们得到表达式1
arr[i][j]==*(&arr[i][j])或*(arr[i]+j)--
另外,在一维数组中,arr[i]=&arr[i]的地址或(arr+i)第i个位置的值都可通过下面的表达式2得到
arr[i]==*(arr[i])==*(arr+i)
所以,利用表达式2替换表达式1可得到:
*(arr[i]+j)==*(*(arr+i)+j)
在处理多维数时,编译器解释该表达式会有一个微小差异。对于一维数组,*(arr+i)会得到第i个索引的值,而对于二维数组,表达式同样会得到第i行的地址。
通过前面可以知道,二维数组是一个一维指针的堆叠,可用下图表示:
我们能用指针数组访问数组元素。对于int arr[5][5];也可写作int (arrptr)[5]=arr;
示例:得到一个一维数组的基地址。
#include <stdio.h> #include <string.h> int main() { int data[5][5]; int i,j; int count = 0; int (*aptr)[5]; int *dataptr; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { data[i][j] =count++ ; } } aptr = data; for (i = 0; i < 5; i++) { printf("%dth row =%p\n", i, *aptr++); } return 0; }
结果为:
在上面的代码中,指针数组用于指向每行(即*aptr)的基地址。
示例:利用指针变量数组访问二维数组的值。
#include <stdio.h> #include <string.h> int main() { int data[5][5]; int i,j; int count = 0; int (*aptr)[5]; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { data[i][j] =count++ ; } } aptr = data; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { printf("%d,%d=%p val=%d\n",i, j, (*aptr+j),*(*aptr + j)); } printf("\n"); aptr++; } return 0; }
程序运行结果如下:
在上面的代码中,指针指向每行的基地址,就可将第2个索引作为偏移量访问数组的各个元素。所以(*aptr+j)会得到第i行和第j列的地址。*(*aptr+j)会得到第i行第j列的地址的值。
通过指针变量访问二维数组的索引
这里是利用指针变量访问二维数组索引。这和如何利用指针变量访问一维数组的方法相似。为此需将每行的地址赋值给指针变量。
#include <stdio.h> #include <string.h> int main() { int data[5][5]; int i,j; int count = 0; int (*aptr)[5]; int *dataptr; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { data[i][j] =count++ ; } } aptr = data; for (i = 0; i < 5; i++) { printf("Address of %d row =%p\n", i, (*aptr+i)); dataptr = (*aptr + i * 5); for (j = 0; j < 5; j++) { printf("%d,%d=%p val=%d\n ",i, j, dataptr,*(dataptr)); dataptr++; } printf("\n"); } return 0; }
程序运行结果:
上面代码中,每一行的地址被分配给整数指针int *dataptr.
dataptr=(*aptr+i*5);
然后通过*(dataptr)和dataptr++累加得到该行索引的值,直到每行结束。
(3)三维数组排列
类似的,三位数组也可看作数组的堆叠,但有一个例外,这里每个堆叠元素为一个二维数组。在线性图中,三维数组排列如下:
三维数组基础
指定三位数组需要三个索引,如下所述。假定三维数组元素为整数,其大小为5,5,5,定义为:
int data[5][5][5];
示例:下面代码演示了如何通过三维数组元素。
#include <stdio.h> #include <string.h> int main() { int data[3][3][3]; int i,j,k; int count = 0; for (i = 0; i <3 ; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { data[i][j][k] = count++; } } } for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { printf("%d%d%d=%d ", i, j,k, data[i][j][k]); } printf("\n"); } printf("\n"); } return 0; }
程序运行结果如下:
例子中三维数组结构如下:
理解三维数组的表达及含义
假定一个三维数组声明为int data[5][5][5]。该数组变量名创建第0行的地址。由于三维数组的每行都包含一个二维数组,这也是一维数组第0行的地址,即一维数组(0,0,0)第0个元素的地址。
#include <stdio.h> #include <string.h> int main() { int data[3][3][3]; int i,j,k; int count = 0; for (i = 0; i <3 ; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { data[i][j][k] = count++; } } } printf("0th row of 3d array=%p\n", data); printf("0th row of 2d array=%p\n", data[0][0]); printf("0th row of 1d array=%p\n", &data[0][0][0]); return 0; }
运行结果如下
这个示例可以得出以下结论:
<变量名>==三维数组第0行的地址
<变量名>[0][0]==二维数组第0行的地址
&<变量名>[0][0][0]==一维数组第个元素的地址
他们的表示的是同一个元素的地址。
数组运算
下面表达式的结果为与第0个元素距离某个偏移量位置元素的地址。
<变量数组名>+偏移量
处理三维数组时有三层间接寻址。在第一层上,可以看到三维数组以二位数组形式排列。因此,如果该层上加个偏移量,他会加二维数组大小的偏移量。
#include <stdio.h> #include <string.h> int main() { int data[3][3][3]; int i,j,k; int count = 0; for (i = 0; i <3 ; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { data[i][j][k] = count++; } } } for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { printf("%d%d%d=%d addr %p ", i, j,k, data[i][j][k],&data[i][j][k]); } printf("\n"); } printf("\n"); } printf("Index value address\n"); for (i = 0; i < 3; i++) { printf("row %d addr=%p\n", i, data + i); } return 0; }
程序运行结果:
如上输出结果所示,索引0,0,0的值为第0行的地址,同样100为第1行地址,200为第二行的地址。
表达式data[i]与data+i等效,都能得到二维数组的第i行的索引。
第二层上,我们发现二维数组为一维数组的堆叠。通过解引用第一层表达式得到第二层,所以*(data[i]+i)与data[i][j]等效,都指向二维数组。而且,*data[i]得到三维数组每个元素的第i个二维数组的基地址。
#include <stdio.h> #include <string.h> int main() { int data[3][3][3]; int i,j,k; int count = 0; for (i = 0; i <3 ; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { data[i][j][k] = count++; } } } for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { printf("%d%d%d=%d addr %p ", i, j,k, data[i][j][k],&data[i][j][k]); } printf("\n"); } printf("\n"); } for (i = 0; i < 3; i++) { printf("row %d addr=%p\n", i, data[0][i]); } printf("2D row address\n"); for (i = 0; i < 3; i++) { printf("3D %d ROW\n", i); for (j = 0; j < 3; j++) { printf("2D row %d addr=%p%p\n", j, data[i][j], *(data[i] + j)); } } return 0; }
运行结果:
在第三层上,当解引用它时我们会看到一维数组及它们的值。通过解引用第二层表达式得到第三层,所以(*(data[i]+i)+k)与data[i][j]+k等效都指向一维数组元素。而要通过“取值"操作符得到这些地址存储的元素。
*((*(data[i]+i)+k))==*(data[i][j]+k)=d[i][j][k]
下面通过一个例子演示如何使用上述表达式。
#include <stdio.h> #include <string.h> int main() { int data[3][3][3]; int i,j,k; int count = 0; for (i = 0; i <3 ; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { data[i][j][k] = count++; } } } printf("Index=val addr<>\n"); for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { printf("%d%d%d=%d addr %p ", i, j,k, data[i][j][k],&data[i][j][k]); } printf("\n"); } printf("\n"); } for (i = 0; i < 3; i++) { printf("row %d addr=%p\n", i, data[0][i]); } printf("2D row address\n"); for (i = 0; i < 3; i++) { printf("3D %d ROW\n", i); for (j = 0; j < 3; j++) { printf("2D row %d addr=%p%p\n", j, data[i][j], *(data[i] + j)); } } printf("1D element address\n"); for (i = 0; i < 3; i++) { printf("3D %d ROW\n", i); for (j = 0; j < 3; j++) { printf("2D %d ROW\n", j); for (k = 0; k < 3; k++) { printf("%d%d%d=%p val=%d", j, j, k, *(data[i] + j) + k, *(*(data[i] + j) + k)); } printf("\n"); } } return 0; }
运行结果如下:
使用指针变量访问三维数组的每个元素
访问三维数组元素与访问二维数组元素的技术是相同的。指针变量必须指向单维数组的基地址,也是每个二位数组的一部分。反过来,该二维数组是整个三维数组的单个元素。
示例如下:
#include <stdio.h> #include <string.h> int main() { int data[3][3][3]; int i,j,k; int count = 0; int *dataptr = NULL; for (i = 0; i <3 ; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) { data[i][j][k] = count++; } } } for (i = 0; i < 3; i++) { printf("3D %d ROW\n", i); for (j = 0; j < 3; j++) { printf("2D %d row\n", j); dataptr = *(data[i] + j); for (k = 0; k < 3; k++) { printf("%d%d%d=%d addr %p ", i, j,k, dataptr,*dataptr++); } printf("\n"); } } return 0; }
运行结果如下:
在上述代码中,表达式*(data[i]+j)得到第i个二维数组的行,该二维数组行指向第j个一维数组行的基地址。表达式dataptr=*(data[i]+j)中指针变量dataptr指向一维数组,我们可以通过解引用相同的*dataptr访问其值,也可食用指针变量自增操作符(即*dataptr++)遍历整个数组。