深入解析二维数组传参的本质
目录
一、理解二维数组的传参
二、二维数组的本质
1、要理解二维数组传参的其他形式,首先需要深入理解二维数组的本质:
2、根据"数组名是数组首元素地址"的规则:
三、二维数组传参的指针形式
解析 *(*(p + i) + j) 的工作原理
1. 理解指针 p 的类型
2. 分解 *(*(p + i) + j)
第一步:(p + i)
第二步:*(p + i)
第三步:*(p + i) + j
第四步:*(*(p + i) + j)
3. 内存布局示例
4. 等价关系
5. 为什么这样设计?
四、关键点总结
五、数组传参知识点整理和回顾
1、一维数组传参
2、二维数组的理解
3、二维数组传参
4、关键点
一、理解二维数组的传参
当我们理解了数组指针的概念后,就能更好地理解二维数组传参的本质了。传统上,当我们需要将一个二维数组传递给函数时,通常会这样写:
#include <stdio.h>void test(int a[3][5], int r, int c) {int i = 0;int j = 0;for(i = 0; i < r; i++) {for(j = 0; j < c; j++) {printf("%d ", a[i][j]);}printf("\n");}
}int main() {int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}};test(arr, 3, 5);return 0;
}
在这个例子中,实参是二维数组,形参也写成二维数组的形式。但实际上,还有其他更本质的写法。
二、二维数组的本质
1、要理解二维数组传参的其他形式,首先需要深入理解二维数组的本质:
-
二维数组可以看作是一个"元素为一维数组"的数组
-
二维数组的每个元素实际上是一个一维数组
-
因此,二维数组的首元素是它的第一行,即一个一维数组
2、根据"数组名是数组首元素地址"的规则:
-
二维数组的数组名表示的是第一行的地址
-
这个地址是一个"一维数组"的地址
-
对于
int arr[3][5]
,第一行的类型是int [5]
-
因此第一行地址的类型就是数组指针类型
int(*)[5]
三、二维数组传参的指针形式
既然二维数组传参本质上传递的是第一行这个一维数组的地址,那么形参也可以写成指针形式:
#include <stdio.h>void test(int (*p)[5], int r, int c) {int i = 0;int j = 0;for(i = 0; i < r; i++) {for(j = 0; j < c; j++) {printf("%d ", *(*(p + i) + j)); // 等价于p[i][j]}printf("\n");}
}int main() {int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}};test(arr, 3, 5);return 0;
}
解析 *(*(p + i) + j)
的工作原理
这段代码中的 *(*(p + i) + j)
是访问二维数组元素的指针表示法,它完全等价于更直观的 p[i][j]
。让我们一步步分解这个表达式的计算过程。
1. 理解指针 p
的类型
首先,p
是一个指向包含5个整数的数组的指针,声明为 int (*p)[5]
。这意味着:
-
p
指向的是二维数组的一整行(一个包含5个int的一维数组) -
p + 1
会移动到下一行的起始位置
2. 分解 *(*(p + i) + j)
让我们从内向外解析这个表达式:
第一步:(p + i)
-
p
指向二维数组的第0行 -
p + i
指向二维数组的第i行 -
结果仍然是一个指向一维数组(行)的指针
第二步:*(p + i)
-
对行指针解引用,得到该行的首元素地址
-
*(p + i)
等价于p[i]
,它表示第i行的第一个元素的地址 -
现在这个表达式降级为一个指向整数的指针(
int*
)
第三步:*(p + i) + j
-
现在我们在行内移动,
+j
表示移动到该行的第j个元素 -
这相当于
&p[i][j]
,即第i行第j列元素的地址
第四步:*(*(p + i) + j)
-
最后解引用这个地址,得到实际的整数值
-
这就是我们想要的
p[i][j]
的值
3. 内存布局示例
假设我们的二维数组在内存中的布局如下(地址是假设的):
行0: [1(1000), 2(1004), 3(1008), 4(1012), 5(1016)]
行1: [2(1020), 3(1024), 4(1028), 5(1032), 6(1036)]
行2: [3(1040), 4(1044), 5(1048), 6(1052), 7(1056)]
-
p
初始指向行0,值为1000 -
p + 1
指向行1,值为1020(因为一行有5个int,每个int 4字节,共20字节) -
*(p + 1)
得到行1的首地址1020 -
*(p + 1) + 2
得到1020 + 2*4 = 1028(行1的第2个元素) -
*(*(p + 1) + 2)
解引用1028,得到值4
4. 等价关系
以下表达式都是等价的:
-
*(*(p + i) + j)
-
p[i][j]
-
*(p[i] + j)
-
(*(p + i))[j]
5. 为什么这样设计?
这种设计反映了C语言中数组和指针的紧密关系:
-
二维数组是按行优先顺序存储的
-
数组名在大多数情况下会退化为指针
-
指针算术运算会自动考虑所指向类型的大小
理解这种指针表示法对于处理动态分配的二维数组、函数参数传递等高级场景非常重要。
四、关键点总结
-
二维数组传参的本质:传递的是第一行一维数组的地址
-
形参的两种写法:
-
数组形式:
int a[][5]
(第二个维度不能省略)或int a[3][5]
-
指针形式:
int (*p)[5]
-
-
注意事项:
-
第二维的大小必须指定,编译器需要知道每一行的大小
-
第一维的大小可以省略
-
指针形式更直接反映了传参的本质
-
理解这一点对于处理多维数组和指针操作非常重要,特别是在需要动态分配内存或处理数组的数组时。
五、数组传参知识点整理和回顾
1、一维数组传参
-
数组名是首元素的地址
-
传参时传递的是首元素的地址
-
函数参数可以写成数组形式或指针形式
2、二维数组的理解
-
二维数组可以理解为一维数组的数组
-
每一行是一个一维数组
-
二维数组的首元素是第一行(整个一维数组)
-
数组名是第一行的地址(即一维数组的地址)
3、二维数组传参
-
传参时传递的是第一行这个一维数组的地址
-
形参可以写成数组形式或指针形式
4、关键点
-
无论一维还是二维数组传参,都需要深入理解数组名的含义
-
数组名代表的是首元素的地址,但二维数组的"首元素"是整个第一行
-
参数形式灵活,可以用数组语法或指针语法表示