数组指针与函数指针(让你从此爱上指针)

引言

在前面的内容中我们已经了解过指针变量的定义:就是存放数据地址的变量。并且了解了一些指针类型以及其存在的意义。
但是如果一个指针变量指向的内容是一个数组或是一个函数,那它的类型是什么呢?我们不妨来分析一下:

char* pc = NULL;         //字符指针
short* ps = NULL;        //短整型指针
int* pi = NULL;          //整形指针
long* pl = NULL;         //长整型指针
long long* pll = NULL;   //更长整形指针
float* pf = NULL;        //单精度浮点型指针
double* pd = NULL;       //双精度浮点型指针

不难看出,指针类型去掉 * 就是这个指针类型解引用时可以访问的空间大小的类型。比如:int* 型的指针变量解引用后可以访问的空间大小就是一个整形变量的大小,也就是四个字节。

说得草率一点:指针类型去掉 * 就是这个指针类型解引用时指向的数据的类型。就不难联想到,数组指针的指针类型就是数组的类型加上 * ;函数指针的指针类型就是函数的类型加上*。接下来就分别了解数组指针与函数指针。

数组指针

定义数组指针

数组指针就是指向数组的指针,它的类型就是数组的类型加上*。所以我们首先要了解数组的类型是什么(需要注意的是:这里说的是数组的类型,不是数组中元素的类型):
这里直接告诉大家:数组的类型就是数组的定义除去数组名以外的部分(包括数组内存放的元素类型与元素个数)。为方便说明,这里定义一个数组arr:

int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };

上述数组 arr 有10个元素,每个元素的类型是都是int型。所以这个数组arr类型就是int [10] 。那么指向这个数组的指针,也就是 &arr 的指针类型就是 int(*)[10] (注:因为 [ ] 的优先级大于* 所以这里的 * 与数组指针的变量名要括起来,表示这是一个指针)。
&arr就是这个arr数组的地址,我们可以创建一个数组指针变量去存放这个地址:

#include<stdio.h>
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int(*parr)[10] = &arr;
	printf("%p\n", &arr);
	printf("%p\n", parr);
	return 0;
}

运行结果

数组指针类型

我们知道,指针类型决定着指针的步长与解引用时可以访问的空间。
对于上面这个数组指针parr,它的步长就是数组arr的字节数,也就是4*10=40个字节;*parr可以找到整个数组。通过解析下面这段代码可以更好的理解数组指针:

#include<stdio.h>
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int(*parr)[10] = &arr;
	int* p = (int*)(parr + 1);
	printf("%d\n", *(p - 1));
	return 0;
}

运行结果
在这段代码中,首先将&arr赋值给数组指针parr,这个数组指针的类型是int(*)[10],步长为40。所以当parr+1时这个指针会跳过整个数组,指向这个arr数组后的一块长度为40个字节的空间:
指针指向图示
当parr+1被强制类型转换为int*再赋给int*型的p指针后,这个指针所指向的位置没有发生变化但是其所指向空间的大小变为了一个int的大小也就是4个字节。
当指针p再减1后所指向的位置就是数组arr的最后一个元素0。%d打印*(p-1)的结果就是0。

数组指针数组

数组指针数组就是将数组指针存放在数组中。在这之前,我们需要先学习如何将一个指针变量存放在数组中:

指针数组

前面提过数组是一组相同类型元素的集合。前面说的比较模糊,这里的相同类型元素在哪里体现?我们从数组的定义中找一找规律:

char arr[10];        //字符数组
short arr[10];       //短整型数组
int arr[10];         //整型数组
long arr[10];        //长整型数组
long long arr[10];   //更长的整形数组
float arr[10];       //单精度浮点型数组
double arr[10];      //双精度浮点型数组

不难发现,一个数组的定义去掉数组名与数组中存储的元素个数后, 剩下的就是这个数组中存储的数据的类型。
如果一个数组中存的是指针类型那么自然就可以写出这样的数组定义:int* arr[10]。这就代表着 arr 是一个数组,数组中有10个元素,每个元素都是 int* 型的(注意:数组指针 int (*arr)[10] 与指针数组 int *arr[10] 的区别。含义不同的原因在于变量名先于谁结合:先与 * 结合表明这是个指针;先与 [10] 结合表明这是个数组):

#include<stdio.h>
int main()
{
	int arr1[4] = { 1, 2, 3, 4 };
	int arr2[5] = { 5, 6, 7, 8 };
	int arr3[] = { 0 };
	int a = 0;
	int* pa = &a;
	int*sarr[] = { arr1, arr2, arr3, pa };
	return 0;
}

只要是一个int* 型的指针,就可以存储在这个指针数组sarr中。

数组指针数组

上面我们得出的结论是,数组的定义去掉数组名与数组中存储的元素个数后, 剩下的就是这个数组中存储的数据的类型。那么当这个数组中存储的元素是数组指针时:首先这是一个数组,所以数组名应该先与 [ ] 结合,其次这个数组中的元素类型是数组指针类型,(先假设是整形数组指针)也就是 int (*)[] ,把它们结合起来就是:int (* [ ])[ ] 。

int main()
{
	int arr1[4] = { 1, 2, 3, 4 };
	int arr2[4] = { 5, 6, 7, 8 };
	int arr3[4] = { 0 };
	int(*parr[3])[4] = { &arr1, &arr2, &arr3 };
	return 0;
}

这里的数组 parr 就是一个整形数组指针数组,他表示一个数组,数组有3个元素,数组中存储的元素类型是整形数组指针,这个整型数组指针指向的数组有4个元素,其中存储的元素类型是整形。

到此。我们就实现了将数组指针存放在数组中。大家可以尝试写一下 “指向指向指向数组指针数组的指针数组的指针数组的指针数组” ,多有意思。

函数指针

定义函数指针

函数指针就是指向函数的指针,它的类型就是函数的类型加上*。所以我们首先要了解函数的类型是什么:
函数的类型就是函数的声明去掉函数名(包括函数的返回值与参数的类型)。为方便说明,这里声明一个add函数:

int add(int, int);

上述add函数的返回值是int,有两个int型的参数。所以这个add函数的类型就是 int (int,int) 。&add就是这个add函数的地址,我们可以创建一个函数指针变量pf去存放这个地址:

int add(int a, int b)
{
	return a + b;
}
int main()
{
	int(*pf)(int, int) = &add;
	return 0;
}

但其实,函数的地址就等于函数名。也就是说对函数指针pf这样赋值也是正确的:

int(*pf)(int, int) = add;

也就是说,这里的pf和add是相等的。所以,我们在调用这个函数的时候可以使用*pf(a,b),也可以直接pf(a,b):

int add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	int(*pf)(int, int) = add;
	int c = add(a, b);
	printf("%d\n", c);
	c = pf(a, b);
	printf("%d\n", c);
	c = (*pf)(a, b);
	printf("%d\n", c);
	return 0;
}

运行结果
当然,在main函数中我们没有必要去使用和函数指针调用函数,直接调用即可。函数指针主要是指向一个回调函数被使用:

回调函数

回调函数是被另一方通过函数指针调用的函数。
我们可以模拟实现一个计算器来简单展示一下回调函数的应用:

#include<stdio.h>
void menu()
{
	printf("*********************\n");
	printf("****  1.+   2.-  ****\n");
	printf("****  3.*   4./  ****\n");
	printf("****   0.exit    ****\n");
	printf("*********************\n");
}
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mel(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void calc(int(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:");
	scanf("%d%d", &x, &y);
	printf("%d\n", pf(x, y));
}
int main()
{
	menu();
	int input = 0;
	do
	{	
		printf("请选择:");
	    scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mel);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			printf("输入错误请重新输入:\n");
			break;
		}
	} while (input);
	return 0;
}

运行结果
在这段代码中,函数Add、Sub、Mel、Div都作为参数传递给calc函数,再由cale函数通过函数指针调用这些函数。Add、Sub、Mel、Div这四个函数就被称为回调函数。
在这个例子中,如果将函数直接在main函数中调用就会出现许多重复的相似的代码。将其作为回调函数调用,可以减少代码的冗余。
在对库函数qsort的使用以及bubble_qsort的实现中大家可能会更加清晰的了解回调函数的应用:
可以参考我的另一篇博客:
原文链接:qsort函数的使用及其冒泡排序版本实现(超详解哦)

函数指针数组

结合上面的函数指针与数组指针数组的知识,我们也可以实现将函数指针存放在数组中:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mel(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
int main()
{
	int(*pf[4])(int, int) = { Add, Sub, Mel, Div };
	printf("%d\n", pf[2](2, 3));
	return 0;
}

运行结果
函数指针数组pf有4个元素,每个元素都是int(* )(int, int)类型的函数指针。当然,我们也可以通过这个函数指针数组去访问函数。
需要注意的是,函数指针数组的形式必须用一个括号括住*pf[4]:int(*pf[4])(int, int)。而不能直接写 int*pf[4](int, int),这个东西算是一个函数数组,数组元素是返回值为int*,参数为两个int的函数。但是函数数组是不被允许的!这里要特别注意。

总结

在这篇博客中,我们了解了数组指针、函数指针、指针数组、数组指针数组、函数指针数组以及一些和这些内容相关联的知识。
后续会出一些指针的练习题解析,xdm持续关注哦
如果对本文有任何问题,欢迎在评论区进行讨论哦
希望与大家共同进步哦

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿qiu不熬夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值