最近在学习C语言,着实遇见了让我非常头疼的问题,什么是指针数组?什么又是数组指针?函数指针又是个什么鬼?
在学习这些之前,我们先来回顾一下指针的相关内容,指针是一个存放地址的变量,指针的大小是固定的,在32位机下是4个字节,在64位机下是8个字节,指针是有类型的,比如整形指针,字符型指针,指针的类型决定了指针的加减整数的步长,指针解引用操作时候的权限。接下来就让我们来深入学习一下指针吧。
指针的进阶之旅
(一)指针数组
存放指针的数组,本质上是一个数组, 不同的类型代表存放的指针的类型。
例如
char* arr[10] //存放字符型指针的数组
int* arr[10] // 存放整型指针的数组
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
int* parr[3] = { &a,&b,&c};
for (int i = 0;i < 3;i++)
{
printf("%d", *(parr[i]));
}
return 0;
}
#include<stdio.h>
int main()
{
int arr1[3] = { 1,2,3 };
int arr2[3] = { 2,3,4 };
int arr3[3] = { 3,4,5 };
int* parr[3] = { arr1,arr2,arr3 };
for (int i = 0;i < 3;i++)
{
for (int j = 0;j < 3;j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
在第一个例子中指针数组parr[3]在内存中开辟连续的三个空间,分别存放a,b,c的地址,打印的结果是a,b,c的值;在第二个例子中指针数组parr[3]存放的则是数组arr1,arr2,arr3的首元素地址,打印的结果是整个数组arr1,arr2,arr3的值,其中的parr[i][j]等价于*(parr[i]+j)。
#include<stdio.h>
int main()
{
const char* parr[3] = { "abcdef","zhang","wang" };
for (int i = 0;i < 3;i++)
{
printf("%s ", parr[i]);
}
return 0;
}
以此类推,此时的指针数组parr[3]存放的则是三个常量字符串首元素的地址,打印出来则是三个常量字符串。
(二)数组指针
本质上是指针,指向数组的指针
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int* p = arr;//arr是首元素的地址
int(*parr)[10] = &arr;//&arr是整个数组的地址,存放在指针数组里
return 0;
}
整型的地址存放在整形指针中,字符型的地址存放在字符型指针中,同样,数组的地址存放在指针数组中。整型指针的类型是int*,整型指针数组的类型是int*[10],变量的类型就是在其基础上去掉相应的自己取的名称,那么整组指针的类型则是int(*)[10]。
数组名和&数组名之间的区别
数组名是数组首元素的地址,&数组名是整个数组的地址
#include<stdio.h>
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n\n", &arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]+1);
printf("%p\n\n", &arr+1);
return 0;
}
打印出来的结果下图所示,
![](https://img-blog.csdnimg.cn/9072f456c3a94556b983176e47afd73d.png)
在图片中,我们发现,前三个的地址完全相同,但是加一后的地址则存在差别, 指针类型决定指针加一产生的效果,类型不同效果不同,arr是首元素的地址,类型是int*,arr[0]的类型是int,取地址之后变成int类型,&arr是整个数组的地址,类型是int()arr,所有指针加一,前两个分别都加4,而数组指针则加整个数组的大小,在这里就是40。
数组名是首元素地址的例外
第一种情况:sizeof(数组名),这里的数组名表示的是整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节。
第二种情况:&数组名,这里的数组名表示的是整个数组,而不是首元素的地址,&数组名则表示的是整个数组的地址。
数组指针的应用
我们先来看一个反面的例子。
#include<stdio.h>
void Print(int arr[], int sz)
{
for (int i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
Print(arr,sz);
return 0;
}
或者Print函数写成这样。
void Print(int* arr, int sz)
{
for (int i = 0;i < sz;i++)
{
printf("%d ", *(arr+i));
}
}
这样一来通过传递地址从而找到数组中的各个元素。学习了数组指针后,我们想,是不是也可以传递整个数组的地址随后再打印出来呢?其实这种方法不是太好,我们接着来修改上面的例子。
Print(&arr,sz);
void Print(int (*parr)[10], int sz)
{
for(int i = 0;i < sz;i++)
{
printf("%d\n",*(parr+i));//error
}
return 0;
}
这样得到的结果却是这样的;
![](https://img-blog.csdnimg.cn/12117a69b94549fb8c80050444ef1feb.png)
前面我们知道指针类型决定指针加一产生的效果,类型不同效果不同,在这里parr加一,向前移动的是整个数组的步长,所以得到的结果不是期望的数组打印。
将打印代码做修改
void Print(int (*parr)[10], int sz)
{
for(int i = 0;i < sz;i++)
{
printf("%d\n", parr[0][i]);
//或者写成printf("%d",*(parr+0)[i]);
//或者写成printf("%d",(*parr)[i]);
//在这里*(parr+0)就等价于 parr[0]指向的是第一行的元素。
}
return 0;
}
得到正确的输出。数组指针的正确应用体现在二位数组的传参上
#include<stdio.h>
void Print(int arr[3][4], int i.int j)
{
for (int i = 0;i < 3;i++)
{
for(int j = 0;j < 4;j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
Print(arr,3,4);
return 0;
}
void Print(int (*parr)[4], int i.int j)
{
for (int i = 0;i < 3;i++)
{
for(int j = 0;j < 4;j++)
{
printf("%d ", *(*(parr+i)+j);
}
printf("\n");
}
}
通过数组指针传递二维数组的参数。
(三)数组传参和指针传参
一维数组传参
#include<stdio.h>
void Print(int arr[])
{
//或者void Print(int arr[5])
//或者void Print(int* parr)
//
}
int main()
{
int arr[5] = { 1,2,3,4,5 };
Print(arr);
return 0;
}
#include<stdio.h>
void Print(int* arr[5])
{
//或者void Print(int* arr[])
//或者void Print(int** parr)传递的是首元素的地址,每个元素是一个整型指针
}
int main()
{
int* arr[5] = { 0 };
Print(arr);
return 0;
}
二维数组传参
#include<stdio.h>
void Print(int arr[3][4])
{
//或者void Print(int arr[][4])
//这里不能写成arr[][],类似于二维数组的定义
//或者void Print(int(* parr)[4])
}
int main()
{
int arr[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
Print(arr);
return 0;
}
这里要注意arr传递的是二维数组首行的地址
一级指针传参
#include<stdio.h>
void Print(int* parr)
{
}
int main()
{
int a=1;
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
Print(&a);
Print(arr);
Print(p);
return 0;
}
二级指针传参`
#include<stdio.h>
void Print(int** parr)
{
}
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* ch[5]={0};
int* p = arr;
int** ppa = &p;
Print(&p);
Print(ppa);
Print(ch);//指针数组的首元素地址,即整型指针的地址。相当于二级指针
return 0;
}
(四)函数指针
函数指针,即存放函数地址的变量,这里要注意,在函数指针的概念下,函数名和&函数名没有任何区别,这两者是完全相同的。函数指针的定义如下
#include<stdio.h>
int Add(int x;int y)
{
return x+y;
}
int main()
{
int (*p)(int ,int ) = &Add;
int (*p)(int ,int ) = Add;
//()内声明参数类型
//p是函数指针变量,用来存放函数指针地址
return 0;
}
去掉名称,即函数指针类型int (*) (int ,int),在定义之后,我们便可以通过函数指针来调用函数。
int main()
{
int (*p)(int ,int ) = &Add;
int (*p)(int ,int ) = Add;
int ret=(*p)(3, 4);//通过函数指针来调用函数
ret=Add( 1, 2);//直接调用函数
ret=p( 4, 5);//这里的p和Add相等
return 0;
}
函数指针数组
即存放函数指针的数组,当函数指针类型相同时,可以创建函数指针数组来存放多个函数的地址。
#include<stdio.h>
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
int main()
{
int (*p[4])(int ,int ) = {Add, Sub, Mul, Div};
return 0;
}
指向函数指针数组的指针
int (*p)(int, int);//函数指针
int (*p [5])(int, int);//函数指针数组
int (*(*p) [5])(int, int);//指向函数指针数组的指针
函数回调
通过函数指针再次调用函数。
#include<stdio.h>
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
void Cal(int (*p)(int, int),int a, int b)
{
int ret = p(a, b);
printf("%d", ret);
}
int main()
{
int x, input, a, b;
int (*p[3])(int, int) = { 0, Add, Sub };
scanf("%d", &input);//输入选择值,选择1进行加法,2进行减法
scanf("%d %d", &a, &b);//输入要进行运算的两个数字
switch (input)
{
case 1:
Cal(p[input],a,b);
break;
case 2:
Cal(p[input],a,b);
break;
}
return 0;
}
函数回调的例子,也可以参考qsor函数在冒泡排序中的应用。