想要彻底理解:建立在知道如何画内存四区图的基础上,如果不是特别清楚什么是内存四区,建议先看下这篇文章:变量的本质分析、内存四区、函数调用模型_睡不饱的小默的博客-CSDN博客
在讲解之前:我们先聊下什么是数组类型,不然后面很难搞懂数组指针。
数组类型:数组的类型由元素类型和数组大小共同决定的,也就是说int[5]、int[6]、char[5]是三种不同的数组类型。(int array[5]的类型为int[5])
1. //定义数组类型,并用int为元素类型 2. 3. typedef int(MYINT5)[5]; 4. 5. int main() 6. { 7. MYINT array; //定义了一个元素类型为int,大小为5的数组类型 8. 9. for(int i=0; i<5;i++) 10. { 11. array[i] = i; 12. printf("array[%d]:%d\n",i,array[i]); 13. } 14. 15. 16. return 0; 17. }
好,现在我们搞清楚了什么是数组类型。
接下来我们进入正题:什么是数组指针和指针数组?
数组指针
定义:指向数组的指针
补充一个知识,方便我们更容易的理解什么叫指向数组:
举例:int a[10]
数组名是数组首元素的起始地址,但不是数组的起始地址 。[a]
那什么才是数组的起始地址呢?【&a】
1. //我们可以通过以下程序进行验证 2. 3. int main() 4. { 5. int a[10]; 6. printf("a:%d \n",a); 7. printf("&a:%d \n,&a); 8. 9. //a = &a-->因为他们的值都等于 &a[0] 10. //a实际上与&a[0]等效 11. 12. //我们分别将他们加1 13. printf("a+1:%d \n",a+1); 14. printf("&a+1:%d \n",&a+1); 15. 16. //发现a+1对于a,增加了 4 17. //而&a+1,对于&a增加了 40 18. //--->通过上述例子可以更好的理解什么是数组类型了,哈哈 19. 20. }
假设数组指针p是一个指向int a[10]的数组类型,很明显p+1与p相差了40
那我们如何定义一个数组指针呢:
int *p = a; ? ---->那么p+1与显然只相隔了 4
int *p = &a; ---->报错,起始&a相当与一个二级指针
//定义数组类型,并用int为元素类型 typedef int(MYINT5)[5]; int main() { MYINT array; //定义了一个元素类型为int,大小为5的数组类型 for(int i=0; i<5;i++) { array[i] = i; printf("array[%d]:%d\n",i,array[i]); } return 0; }
指针数组
定义:一个由指针组成的数组。// int *p[];
废话不多说,直接上图:(如果看不懂下图:先看变量的本质分析、内存四区、函数调用模型_睡不饱的小默的博客-CSDN博客)
为了加深我们的理解:
接下来我们从多维数组的本质上进行刨析:
int myarray[3][5] 与 int (*p)[5]?
myarray===>到底代表了什么?
为什么数组 char a[i][j] ==>*(*(a+i)+j)?
先给出答案然后我们再进行案例测试:
myarray-->是一个数组指针,指向低维数组的指针,每次往后跳一维的长度(可以理解为一行)。
补充:为了方便理解,我们不妨将 [ ]和 *理解为降维(即取地址里的内容),&为升维
假设:a[3][5]
a ---> &a[0]
a+1 ---> &a[1]
a+0 ----> &a[0]
*(a + 0) + 0--> &a[0][0]
*(*(a+0)+0) ---> a[0][0]
同理:*(*(a+i)+j) ---> a[i][j]
好的,我们用一段用例来测试一下:
//我们可以通过以下程序进行验证 int main() { int a[10]; printf("a:%d \n",a); printf("&a:%d \n,&a); //a = &a-->因为他们的值都等于 &a[0] //a实际上与&a[0]等效 //我们分别将他们加1 printf("a+1:%d \n",a+1); printf("&a+1:%d \n",&a+1); //发现a+1对于a,增加了 4 //而&a+1,对于&a增加了 40 //--->通过上述例子可以更好的理解什么是数组类型了,哈哈 }
引出:
多维数组做函数参数的退化的原因
//数组指针类型 /* 数组指针--->用于指向一个数组的指针 分清两个概念:数组名代表数组首元素的起始地址,但不是数组的起始地址 a 通过将取地址符&作用于数组可以得到整个数组的起始地址 &a 定义数组指针: 1.通过数组类型定义数组指针 typedef int(ArrayType)[5]; ArrayType *pointer; 2.声明数组指针类型 typedef int(*MyPointer)[5]; MyPoint myPoint; 3.直接定义: int(*pointer)[5]; */ int main() { // 1.通过数组类型定义数组指针 { int a[5]; typedef int(ArrayType)[5]; ArrayType *array; array = &a; for(int i =0 ;i<5;i++) { (*array)[i] = i; printf("array[%d]:%d\n",(*array)[i]); } } // 2.声明数组指针类型 { int a[5]; typedef int(*MyPointer)[5]; MyPointer mypointer; mypointer = &a; for(int i =0 ;i<5;i++) { (*mypointer)[i] = i; printf("mypointer[%d]:%d\n",(*mypointer)[i]); } } //3.直接定义: { int a[5]; int(*Pointer)[5] = NULL; Pointer = &a; for(int i =0 ;i<5;i++) { (*Pointer)[i] = i; printf("Pointer[%d]:%d\n",(*Pointer)[i]); } } return 0; }
为什么会有退化问题的存在:
C/C++编译器只会机械的值拷贝的方式传递参数(实参把值传给形参,地址传递也不过是把地址的值传给了形参)。编译器在处理a[n]的时候,它没有办法知道n是几,它只知道&a[0],它是通过值传递进去的。-->这里就说明了一个东西,api函数的接口一般都是传地址加长度。
int fun(char a[20]) -->虽然函数能够得到20这个数字,但编译器没有这么做
典型的等价关系:
数组参数 等效的指针参数
char a[30] char *
char *a[30] char **
char a[10][30]