指针和二维数组
指针存储的是内存单元的地址,当使用引用运算符 *,或者变址运算符 [ ] 时才能将指针所指向的内存单元中的值取出。
指针有两个关键属性:
1.它存储的是内存地址
2.它存储的是什么类型变量的内存地址,这一点非常重要,这将决定对指针解引用时如何对它所指向的内存单元进行解读。
如果有这样定义的数组和指针变量
int *p;
int a[3]={1,2,3};
p=a;
那么它在内存中的图例是这样的:
图一
变量名称实际就是内存地址的一个代号。
这里需要说明一下,数组名a其实不是一个变量,它是一个常量,即不可以进行加减操作,例如a++也就是a=a+1,这样的赋值操作是不允许的,这里只是为了说明方便称其为变量名。
数组地址的表达方式有以下几种:
a 表示数组a的首地址,也就是0x0000;
&a也可以表示数组的首地址,也是0x0000,
&a[0]也是数组元素的首地址,也是0x0000
例如:这些输出都将是数组a的首地址
printf("%p\n",a);
printf("%p\n", &a);
printf("%p\n",&a[0]);
二维数组和指针
如果a是一个二维数组
int a[2][3]={{1,2,3},{4,5,6}};
并使用二级指针int **p指向它
int **p;
p=a;
二维数组a[2][3]是一个2行3列的矩阵,但是那只是方便人们理解,在计算机内存中没有二维,都是一维,它仍然是线性存储的。此时,内存中的分布是这样的。
图二
这样对指针p赋值的话,编译器会给出警告,因为p和a的类型不同,也就是p+1和a[1]指针向后移动的内存单元数不同。例如这里,p存储的是指针变量的地址也就是指向整形指针的指针变量,而整形指针占4个字节的位置,所以p+1将指向0x0004。而a是二维数组,它的元素是一个一维数组,长度为12个字节,a[1]将指向0x000C。
二维数组的首地址表示方法:
以下几种表示方法都可以表示首地址
a
&a
a[0]
&a[0]
但是要注意,二维数组的a[0]和一维数组中的a[0]不一样。
如图一所示,一维数组的元素是整形变量,它的a[0]是整形值1。
如图二所示,二维数组的元素是一个一维数组,也就是说,每个元素的长度是3*4=12个字节。a[0]表示的是一维数组的地址,也就是指向一维数组的指针,例如这里是0x0000,那么a[1]将是0x000C。
p是一个二级指针,将二维数组a的首地址赋值给它,那么它存储的地址将是0x0000。对p解引用一次*p,当然就取出了0x0000中的值,也就是1,如果执行语句printf(“%d”,(int)*p); 那么得到的结果是1,但这并不是二维数组的正确打开方式。
当对p再次解引用的时候,就出错了(*(*p)),它表示取出内存地址0x0001中的值,那将什么也不是,当然会出错。而且p每增加1,内存地址+4,a每增加1,内存地址+12,所以,无法直接使用二级指针p来正确操作二维数组a。
如何使用指针操作二维数组呢?
可以定义特殊的二级指针,int (*p)[3]来指向二维数组。它是一个二级指针,并且说明了一维数组中元素的个数是3个,这样p指针每+1,内存地址加3*4=12。()不能去掉,因为*比[]运算符优先级低。
p=a; //p=0x0000
p++; //p=0x000C
显示数组元素可以使用这几种写法:
p[行][列]
(* (p+行))[列]
*(* (p+行)+列)
以下程序段将可以正确打印二维数组
#include <stdio.h>
main()
{
int i, j;
int a[2][3] = {
{1,2,3},
{4,5,6}
};
//定义一个特殊的二级指针
int(*p)[3];
p = a;
printf("在主函数中打印数组a[2][3]\n");
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("% d ", p[i][j]);
}
printf("\n");
}
}
如何将一个二维数组作为参数传递给函数:
形式参数要写明数组的数据类型,例如这里是int ,并且是指针变量,而且每行有3个数据,即:
int(*p)[3] ,这样内存每+1,内存单元增长3*4=12
然后,可以在函数中像使用普通数组那样使用指针变量p调用数组中的元素,例如p[i][j]
#include <stdio.h>
func(int(*p)[3])
{
int i, j;
printf("\n在函数func中打印数组a[2][3]\n");
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("% d ", p[i][j]);
}
printf("\n");
}
}
main()
{
int a[2][3] = {
{1,2,3},
{4,5,6}
};
func(a);
}
如何返回一个二维数组
首先,函数的返回值类型要是一个二级指针的形式:
int **func2()
然后,函数的返回值用一个特殊的二级指针接收,改指针指出了每行元素的个数,例如:
int (*p)[3];
表示是一个整型的二级指针,它每行的数据是3个,也就是p每加1,内存单元增长12个。
#include <stdio.h>
int **func2()
{
static int a[2][3] = {
{1,2,3},
{4,5,6},
};
return a;
}
main()
{
int i, j;
int a[2][3] = {
{1,2,3},
{4,5,6}
};
//定义一个特殊的二级指针
int(*p)[3];
p = func2();
printf("\n在主函数中打印函数传递过来的数组\n");
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("% d ", p[i][j]);
}
printf("\n");
}
}
其实这里用一级指针也可以正确返回二维数组的首地址,但是要在主函数中用带有维度的特殊二级指针正确转化一下。
但是这种返回方式编译器会给出警告,如果不想看到警告信息,可以这样返回二维数组:
#include <stdio.h>
int (*func3())[3]
{
static int a[2][3] = {
{1,2,3},
{4,5,6},
};
return a;
}
main()
{
int i, j;
int a[2][3] = {
{1,2,3},
{4,5,6}
};
//定义一个特殊的二级指针
int(*p)[3];
p = func3();
printf("\n在主函数中打印函数传递过来的数组\n");
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("% d ", p[i][j]);
}
printf("\n");
}
}
这样,在返回值中指出了数组的维度,在接收值中也有相同的类型和维度,也就是指针类型完全相同,编译器就不会给出警告了。
传递二维数组,关键是要传递正确的数组首地址,并正确解读其中包含的维度。
参考链接:
https://blog.csdn.net/qq_33573235/article/details/79530792
https://www.cnblogs.com/zhixin/articles/3396789.html
https://blog.csdn.net/qq_39090164/article/details/79425247
https://www.cnblogs.com/qingergege/p/6917913.html