C语言-指针进阶(4.函数指针)

154 阅读5分钟

前言

在指针进阶系列里已经介绍了字符指针、数组指针、指针数组、数组参数、指针参数,本篇将介绍指针进阶的最后一部分-函数指针。

1.函数指针

先看下面的代码

//函数指针
int Add(int x, int y)
{
	return x + y;
}	
int main()
{
	int arr[10] = { 0 };
	int (*parr)[10] = &arr;//取出数组的地址
	//parr是指向数组的指针,存放的的是数组的地址
	
	//函数指针:存放函数地址的指针
	printf(" &Add:%p\n", &Add);
	printf(" Add:%p\n", Add);
	//数组名 != &数组名
	//函数名 == &函数名 == 函数的地址
        
	int (*pf)(int, int) = &Add;
 	int (*pf)(int, int) = Add;//Add === pf
	//pf就是一个函数指针变量
	//函数指针的调用:
	int ret = (*pf)(4, 4);//1
	int ret = pf(3, 3);//2
	int ret = Add(2, 2);//3
	int ret = (********pf)(5, 5);
	//以上三种方式一模一样,在函数指针里,(*pf)里的*只是一个摆设,没有意义,写成(***pf)也一样,注意只有在函数指针调用里才是这样
	//但不能写成int ret= * pf(2,2);
	printf("%d\n", ret);
	return 0;
}

70cd57ec0740f5464c5990dc58078d1d.png

根据上面的结果可知,函数指针是存放函数地址的指针,函数名就是函数的地址,与数组名不同的是,数组名 != &数组名,而函数名等价于&函数名,所以函数名 == &函数名 == 函数的地址。函数指针的调用有很多方式,可以使用一个函数指针变量int (*pf)(int, int) = &Add 这样调用pf和调用Add都是一样的,特殊的是( * pf)的 * 只是一个摆设,没有意义,写成(***pf)也一样,这里的括号不可以省略,注意只有在函数指针调用里才是这样。

2.函数指针数组

//函数指针数组 - 存放函数指针的数组
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pfarr[2])(int,int) = { Add,Sub };//pfarr就是函数指针数组 同类型的函数
	int (*pfarr[2])(int, int) = { pf1,pf2 };
  int(*pfarr[3])(int,int) = { NULL,Add,Sub };//pfarr[1]==Add;pfarr[2]==Sub;
	return 0;
}

函数指针数组是用来存放函数指针的数组,要求必须是同类型的函数,可以存放NULL空指针。为什么pfarr是一个函数指针数组呢?int(pfarr[3])(int,int):pfarr先于[]结合,说明pfarr是一个数组,数组的每一个元素是int()(int,int),即数组的每一个元素都是函数指针,这就是函数指针数组的定义方式。

3.指向函数指针数组的指针

指向函数指针数组的指针

int (*p1)(int, int);//函数指针

int (* p2[3])(int, int);//函数指针数组

int (* (*p3)[3])(int, int) = &p2;//取出的是函数指针数组的地址

p3就是一个指向【函数指针数组】的指针

如何一步步分清变量的类型: 先看一个例子int arr[2];他的数组类型是int[2],每个数组元素类型是int。

再来看int (* (p3)[3])(int, int),p3先于结合,说明是一个指针,(*p3)再与[]结合,说明是一个数组指针,数组的每一个元素类型是int( * )(int,int),即函数指针,所以p3是一个存放函数指针数组的指针。

4.回调函数

回调函数定义:一个通过函数指针调用的函数

回调函数的使用如下:

//回调函数 - 一个通过函数指针调用的函数
int Calc(int (*pf)(int, int))//Calc函数就是一个回调函数
{
	int x, y;
	scanf("%d %d", &x, &y);
	return pf(x, y);
}
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int ret = 0;
	ret = Calc(Add);
	printf("%d\n", ret);
	ret = Calc(Sub);
	printf("%d\n", ret);
	return 0;
}

2ad370a9ad6174ea7f0fd7821c163b7f.png 通过函数名传参给回调函数,回调函数使用函数指针作为形参接收,然后回调函数就可以使用函数指针来调用这个函数。

总结

到这里函数指针就结束了,来两个抽象的题~.~

(*(void (*)())0)();

解释如下:
()0   强制类型转换,这里()内是函数地址类型,是把函数地址强制转换为0 地址
实际上是调用0地址处的函数
该参数无参,返回类型为void
void (*)()  - 函数指针类型
(void (*)())0  - 对0进行强制类型转换,被解释为一个函数地址
* (void (*)())0  - 对0地址进行了解引用操作
(*(void (*)())0)()  - 调用0地址处的函数

2.

void (*signal(int, void (*)(int)))(int);

解释如下:
void(*)(int) signal(int, void (*)(int));//实际上不能这样编写 
 typedef void(*pfun_t)(int);  - 对void (*)()的函数指针类型重命名为pfun_t
 也就等价于 pfun_t signal(int,pfun_t);
1. signal先于()结合 - 说明signal是一个函数名
2. signal函数的第一个参数类型是int ,第二个参数类型是函数指针
	该函数指针,指向一个参数为int,返回类型为void的函数
3. signal函数的返回类型也是一个函数指针
	该函数指针,指向一个参数为int,返回类型为void的函数
signal是一个函数声明

能把上面两个抽象题搞清楚,那么函数指针应该就很清楚了。

到这里指针进阶系列就结束了。