函数形参中的二维数组与二级指针
前言:
二维数组在leetcode给出的模板函数中通过二级指针传递,同时会给出行数Row与列数Col数组,往往在函数中可直接使用a[i][j]遍历二维数组每一个元素。而实际在编写代码时声明的二维数组如果在栈中,此时在函数中通过a[i][j]遍历数组会出现段错误。为了探究其原因,本文对数组指针,指针数组以及函数形参中的二维数组与二级指针进行了总结。
1.数组指针与指针数组
[]运算符优先级高于* 因此[]先与p结合
int *p[10]; // 指针数组 一个大小为10的int *型指针数组
int (*p)[10]; // 数组指针 一个指向大小为10的int型数组指针
2.二维数组中的数组名与解引用
例如:二维数组a[3][3],其中:
a a的首地址 类型为: int (*)[3]
&a a的首地址 类型为: int [3][3]
&a[0] a的首地址 类型为: int (*)[3]
&a[0][0] a的首地址 类型为: int *
由以下代码可验证以上类型声明,同时说明栈中二维数组的内存地址连续排列如同一维数组一样,a[3][3]等价于a[9] = {1,2,3,4,5,6,7,8,9},此时a[2][1]等价于a[1][4],在此处不会发生数组越界或段错误,即a[i][j] = *(*(a+i)+j) = *((int *)a + i*Col + j)成立。
/* 二维数组中的数组名与解引用 */
#include <stdio.h>
int main(){
int a[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
printf("a in stack\n");
printf("a:%p\ta+1:%p\n",a,a+1);
printf("&a:%p\t&a+1:%p\n",&a,&a+1);
printf("&a[0]:%p\t&a[0]+1:%p\n",&a[0],&a[0]+1);
printf("&a[0][0]:%p\t&a[0][0]+1:%p\n",&a[0][0],&a[0][0]+1);
}
上述代码二维数组声明在栈中,而在使用malloc动态声明二维数组时,往往是先声明一个二级指针(int **a)再将每一行子数组的首地址存入,此时a[i][j] = *(*(a + i) + j)一定成立而不一定等于*((int *)a + i*Col + j),因为每一行子数组动态分配的地址空间不一定连续。
#include <stdio.h>
#include <stdlib.h>
int func(int **array, int m, int n) {
printf("array in heap\n");
printf("array[i][j]\t*((int *)array + i * 3 + j)\n");
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
printf("%d\t\t", array[i][j]);
printf("%d", *((int *)array + i * n + j));
printf("\n");
}
}
printf("\n");
return 0;
}
int main() {
int **array = (int **)malloc(sizeof(int *) * 3);
for (int i = 0; i < 3; ++i) {
array[i] = (int *)malloc(sizeof(int) * 3);
for (int j = 0; j < 3; ++j) {
array[i][j] = i * 3 + j + 1;
}
}
func(array,3,3);
return 0;
}
3.函数形参中的二维数组与二级指针
首先需要值得注意的是二维数组与二级指针没有任何关系,然后在向函数传递二维数组时需要注意其是在栈还是在堆中动态分配的。
- 堆分配
在上一节中已经提及了二维数组的堆分配,函数形参通常直接使用二级指针传递,在函数中可以直接通过a[i][j]遍历数组,但不能通过*((int *)a + i *Col + j)遍历。 - 栈分配
如果二维数组是在栈中分配此时情况复杂一些,常规的方式是以下三种形式:
// 1.int (*)[Col]形参类型
void fun(int a[Row][Col]);
void fun(int a[][Col]);
void fun(int (*a)[Col]);
在以上三种方式中,在函数中可以直接通过a[i][j]遍历数组,也可以通过*((int *)a + i *Col + j)遍历数组。除此之外,栈中分配的二维数组也可以通过二级指针传递,然而在这种方式下不能直接通过a[i][j]遍历数组,只能通过*((int *)a + i *Col + j)遍历数组。
// 2.int **形参类型
void fun(int **a);
数组名a的实际类型为int (*)[Col],在使用数组名a作为函数实参时,类型强制转化为int **,a[i][j]的解引用*(*(a + i) + j),实际上等效为**((int *)a + i + j),访问了值为a[i + j]的非法地址造成段错误。为了解决这个问题,可以在遍历前将二级指针(int **)转化为(int (*)[Col])指针,或者在实参传入前,将每一行子数组的首地址存入一个指针数组中。
// 仅适用于Col等长 例如:int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
1.int (*ptr)[Col] = (int (*)[Col])a;
for (int i = 0; i < Row; ++i) {
for (int j = 0; j < Col; ++j) {
printf("%d\t", ptr[i][j]);
}
}
// 不仅适用于Col等长还适用于Col变长 例如:char str[][5] = {"a", "ab", "abc"};
2.int *ptr[Row] = {0};
for (int i = 0; i < Row; ++i) {
ptr[i] = a[i];
}
fun(ptr);