前言

(1)函数指针,顾名思义,就说函数的指针。

(2)我们学习Linux的时候,经常能够看到使用一个结构体,结构体中很多个成员,我们给这些成员赋于函数名。这样非常方便我们管理一个设备文件。

(3)本文将会详细介绍函数指针。

函数指针介绍

函数指针原型

(1)函数指针的原型是不固定的,他根据你的原函数类型所决定。什么意思呢?看代码(2)根据代码,我们发现其实这个函数指针的定义,和所指向的函数是一样的(也就是说,函数指针p与原函数print是一样的,函数指针a与原函数add是一样的)。最终使用的时候,使用方式和原函数也一样。

(3)这个函数指针我们可以理解为,就是将函数名改了一下,原来是print现在是p,原来是add,现在是a。只不过在定义的时候,需要将print改成(*p),add改成(*a)

(4)这个时候,肯定会有人问为什么这么做呢,直接使用原函数名不香吗?后续会讲

void print(void)
{
	printf("test\n");
}

int add(int a, int b)
{
	return a + b;
}

int main()
{
    //我们对比这两个函数指针,发现他们的是根据所指向的函数是一样的
    //我们可以理解为,就是讲函数名改了一下,原来是print现在是p,原来是add,现在是a。
	void(*p)(void) = print;
	int(*a)(int, int) = add;
	p();
	printf("%d\n",a(1, 2));
	return 0;
}

函数指针使用的时候不需要*

 我们看到上面的代码,发现函数指针在定义的时候,需要写成(*p),但是在使用的时候,直接使用p了。我们可以加上*吗?可以的,不过加上之后没用,可以忽略

//下面的结果是一样的
    p();
	(*p)();
	printf("%d\n", a(1, 2));
	printf("%d\n", (*a)(1, 2));

函数指针加深理解

题目一

(1)我们来看下面这个是什么意思。首先,我们从左到右理解,首先是第一个大括号,不用管。之后我们看到*里面是一个(),因为()优先级最高,所以先括号里面的内容。看到括号里面的内容,有没有想到什么?对的,函数指针!这就是一个函数指针。

(2)现在我们可以将下面这个改成(*(函数指针)0)()了。这个时候,我们需要思考了,这个是什么玩意了。我们再次回到上面的函数指针不需要*的内容去。发现什么了吗,  p()==(*p)(),而p又是一个函数指针。

(3)那么现在我们是不是也可以这么理解,我们是在将一个地址为0的函数指针进行调用呢?而(函数指针)0就是将0强制类型转换为一个函数指针。

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

题目二

(1)看了上面这个代码,现在这个是不是非常好理解了。

(2)先看将他理解为一个void(*signal)(int),xxx我们先不管,从大体上来看,他就是一个函数指针。而signal的内容,也能够一眼看出来,也是一个函数指针。他的第二个参数也是一个函数指针。

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

(3)这个函数如何简化呢?typedef就是很好的重命名方式。

//错误写法
typedef void(*)(void) p;

//正确写法
typedef void(*p)(void); //将void(*)(void)函数指针类型冲命名为p

//最终简化结果
void (*signal(int , void(*)(int)))(int) == \
p signal(int, p);

函数指针实操

实操一

我们下面模仿一个计算机的功能。输入对应的数字执行相应功能。我们对比不使用函数指针和使用函数指针,是不是发现使用函数指针之后,代码可读性和简洁程度高了很多。

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a*b;
}
int div(int a, int b)
{
	return a / b;
}

/******* 不使用函数指针  *******/
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}


/******* 使用函数指针  *******/
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	while (input)
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
	}
	return 0;
}

实操二

我们在编写Linux驱动程序的时候,总是能够看到file_operations结构体,其中每个变量都说一个函数指针。我们只需要调用这个结构体中的变量,就可以直接操作到对应的驱动程序。这样在庞大的Linux系统中,会非常方便。

static const struct file_operations  hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,   //hello_read为函数指针
	.write		= hello_write,  //hello_write为函数指针
	.open		= hello_open,   //hello_open为函数指针
    .release    = hello_release //hello_release为函数指针
};