本节讲解下多维数组的一些易错点和 超过2维后的数组简单认识
多维数组的空间想象
- 一维数组:一列长表或一个向量
- 二维数组:一个表格或一个平面矩阵
- 三维数组:一本书或三维空间的一个方阵
- 多维数组: 多维空间的一个数据列阵
C语言在内存中的存放情况
在计算机内存中,一个数组通常是一块连续的内存空间,它按照一定的顺序存储数组元素。数组中的每个元素都有一个唯一的索引,可以用来访问该元素。
具体来说,数组在内存中的存放方式通常是:
数组元素按照定义的顺序在内存中依次排列,每个元素占据一个连续的内存单元。
数组的起始地址即为第一个元素的内存地址,而后续元素的地址则按照元素类型所占用的字节数进行连续的地址递增。
数组的元素可以通过下标访问,下标可以理解为偏移量,即相对于数组首地址的偏移量。
对于多维数组,内存中的存储方式与一维数组类似,只不过多维数组的元素可能需要多个下标来访问。
需要注意的是,不同的编程语言可能对数组的实现方式有所不同,但是大多数语言都采用了类似的内存存储方式。同时,由于内存空间有限,当数组元素过多时,可能会导致内存溢出等问题。
举例:二维数组在内存按行列存储
- int a[3][2];3 行2列,6个元素表示1个3行2列的矩阵
❗易错点:二维数组初始化不能缺少列数,可以省略行
定义接受二维数组作为实参的形参时,定义时可以省略二维数组的行数,但不能省略列数。
简单说查找时候,a[3][4]说明是第三行第四列,知道二维数组是a[][4],知道列数,就可以查询了
a[3][4],知道列是4,直接锁定a[3][4]的地址
因为数组的内存布局是 按照行优先的顺序进行的。也就是说,每一行的元素是连续存储的,而每一列的元素则是间隔存储的,如果不指定列数,编译器无法确定每个元素之间的间隔,也就无法正确地计算出数组中元素的地址。
比如:
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
这里指定了每行有3个元素,编译器就可以根据行数和列数计算出每个元素的地址。
三维数组熟悉
无非是变成x,y,z面三维度了
这里 a[][][] , 后面二个还是xy面,也就是二维数组的行列
三维数组的初始化
//三维数组
int a[2][3][4] =
{
{
{6,2,3,4},
{5,11,15,8},
{8,20,10,11}
},
{
{0,0,3,4},
{5,0,7,8},
{8,1,18,31}
}
这里插入下一个重要的知识点:
‼️ 几维数组指针
例子:
关于几维数组指针:
a[i][j][k][2] 等同于 *(*(*(*(a+i)+j)+k)+2) ?
·
维度太高不好理解,以数组int a[i+1][j+1]二维数组为例。
如何取得最后一个元素a[i][j]的值?
a=*a,即该数组的首地址,亦即第0行的首地址
(如果要取第0行的第0个元素,即a[0][0],可用**a
*(a+i)表示第i行首地址,*(a+i)+j第i行第j列的地址。而*表示引用地址的存储内容。
故*(*(a+i)+j)表示i行j列的元素取值。
所以更高维同理。
—— 这里解释下为什么引用是a?**
对于二维数组来说,先通过a来获取第一维的指针,然后再通过这个指针来获取第二维的元素。
我们不能直接改变访问里面某个值,举例说明:
假设我们定义了一个二维数组a:
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
如果我们想要访问数组a中的某个元素,例如a[1][2],可以直接赋值
int value = a[1][2]; // 直接访问数组元素
也可以使用如下指针:
int **p = a; //指向第一行的指针 = a[0]
int value = *(*(p+1)+2); // 使用指针的指针访问数组元素
p是一个指向指针的指针,它指向了数组a的第一行的指针,也就是指向a[0]的指针。
因此,p+1指向了数组a的第二行的指针,也就是指向a[1]的指针。接着,*(p+1)表示第二行的指针,即a[1],再加上偏移量2,就可以访问a[1][2]这个元素了。
最后直接把它元素值给value值
双重指针的解引用操作 * 在这个表达式中出现了两次。我们可以将它们分开来看:
-
首先是 *(p+1),这里的 p+1 表示将指针 p 的值加上1,也就是让指针指向二维数组的第2行。然后再使用解引用操作符 *,表示获取指向第2行的指针的值,即第2行的首个元素的地址。
-
接着是
*(*(p+1)+2)
,这里的 *(p+1) 表示获取指向第2行的指针的值,即第2行的首个元素的地址。然后再加上2,表示指向该行第3列的元素的地址。最后再使用解引用操作符 *,表示获取该元素的值。
三维数组例子
#include "stdio.h"
void main(){
//三维数组
int a[2][3][4] =
{
{
{6,2,3,4},
{5,11,15,8},
{8,20,10,11}
},
{
{0,0,3,4},
{5,0,7,8},
{8,1,18,31}
}
};
int i, j, k;
int(*p)[3][4]; //p是指向二维数组的指针
p = a; //p指向三维数组的0行 (也即p指向一个二维数组)
//p=&a[0]; //与上等价
printf(" 【三维数组的遍历】:\n\n");
for (i = 0; i < 2; i++) {
for (j = 0; j < 3; j++) {
for (k = 0; k < 4; k++) {
//元素值遍历 : *(*(*(p + i) + j) + k相当于 *a[i][j][k]
printf("a[%d][%d][%d]=%-9d ", i, j, k, *(*(*(p + i) + j) + k));//元素值
printf("a[%d][%d][%d]=%-9d ", i, j, k, *(*(a[i] + j) + k)); //元素值
printf("a[%d][%d][%d]=%-9d ", i, j, k, *(a[i][j] + k)); //元素值
printf("a[%d][%d][%d]=%-9d ", i, j, k, *(&a[i][j][k])); //元素值
printf("a[%d][%d][%d]=%-9d ", i, j, k, a[i][j][k]); //元素值
printf("a[%d][%d][%d]=%-9d\n", i, j, k, p[i][j][k]); //元素值
//元素地址遍历:
// printf("&a[%d][%d][%d]=%-9d ", i, j, k, *(*(p + i) + j) + k); //元素地址
// printf("&a[%d][%d][%d]=%-9d ", i, j, k, *(a[i] + j) + k); //元素地址
// printf("&a[%d][%d][%d]=%-9d ", i, j, k, a[i][j] + k); //元素地址
// printf("&a[%d][%d][%d]=%-9d\n", i, j, k, &a[i][j][k]); //元素地址
//由于少了整体括号,所以出现错误的遍历,只有a[0][0][0],a[0][1][0],a[0][2][0],a[1][0][0],a[1][1][0],a[1][2][0]正确
//
// printf("a[%d][%d][%d]=%-9d ", i, j, k, **(*(p + i) + j) + k); //错误,由于最外层少括号
// printf("a[%d][%d][%d]=%-9d ", i, j, k, **(a[i] + j) + k); //错误,由于最外层少括号
// printf("a[%d][%d][%d]=%-9d ", i, j, k, *a[i][j] + k); //错误,由于最外层少括号
// printf("a[%d][%d][%d]=%-9d\n", i, j, k, *&a[i][j][k]); //正确
}
printf("\n");
}
}
}
【【【注意】】】:在对地址取*运算时, 一定要加“()”,也即对地址整体取“括号”,然后*,否则将得不到期望值!
比如:*(a[i] + j) + k表示 & a[i][j][k] ====> 也即元素a[i][j][k]的地址。
对其取*运算应该是*(*(a[i] + j) + k) ====> 表示是元素a[i][j][k]的值。
而不能写成这样:**(a[i] + j) + k ====> 并不是我们期望的各元素值!
当然了:对于&a[i][j][k]求其元素值时,可以对其直接取*,不必在意()整体的问题!
【分析如下】:
for (i = 0; i < 2; i++) {
for (j = 0; j < 3; j++) {
for (k = 0; k < 4; k++) {
printf("a[%d][%d][%d]=%-9d ", i, j, k, **(a[i] + j) + k); //**(*(p + i) + j) + k, *a[i][j] + k同理
}
}
}
【输出结果】:[6],7,8,9 [5],6,7,8 [8],9,10,11 [0],1,2,3 [5],6,7,8 [8],9,10,11
【很明显】:只有a[0][0][0], a[0][1][0], a[0][2][0], a[1][0][0], a[1][1][0], a[1][2][0]正确,其他值并不是个元素的值!
【假如】:
i=0, j=0, k=0; ==> **(a[i]+j)+k ===> **(a[0]+0)+0 ===> *(*(a[0]+0))+0 ===> *(a[0][0])+0 ===>*(a[0][0])+0 ===> *(&a[0][0][0])+0= 6
i=0, j=0, k=1; ==> **(a[i]+j)+k ===> **(a[0]+0)+1 ===> *(*(a[0]+0))+1 ===> *(a[0][0])+1 ===>*(a[0][0])+1 ===> *(&a[0][0][0])+1= 7
i=0, j=0, k=2; ==> **(a[i]+j)+k ===> **(a[0]+0)+2 ===> *(*(a[0]+0))+2 ===> *(a[0][0])+2 ===>*(a[0][0])+2 ===> *(&a[0][0][0])+2= 8
i=0, j=0, k=3; ==> **(a[i]+j)+k ===> **(a[0]+0)+3 ===> *(*(a[0]+0))+3 ===> *(a[0][0])+3 ===>*(a[0][0])+3 ===> *(&a[0][0][0])+3= 9
其他情况亦是如此!
节选自:三维数组