函数指针、回调函数、转移表

一、函数指针

参考:chenyc4的博客

1.1 定义

函数指针的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。我们知道,函数的定义是存在于代码段,因此,每个函数在代码段中,也有着自己的入口地址,函数指针就是指向代码段中函数入口地址的指针。

1.2 实例

其声明形式如下所示:

ret (*p)(args, ...);

其中,ret为返回值,*p作为一个整体,代表的是指向该函数的指针,args为形参列表。其中p被称为函数指针变量 。

#include <stdio.h>
int max(int a, int b)
{
    return a > b ? a : b;
}
int main(void)
{
    int (*p)(int, int); //函数指针的定义
    //int (*p)();       //函数指针的另一种定义方式,不过不建议使用
    //int (*p)(int a, int b);   //也可以使用这种方式定义函数指针
    
    p = max;    //函数指针初始化
    
    int ret = p(10, 15);    //函数指针的调用
    //int ret = (*max)(10,15);
    //int ret = (*p)(10,15);
    //以上两种写法与第一种写法是等价的,不过建议使用第一种方式
    printf("max = %d \n", ret);
    return 0;
}

警告⚠️: 简单声明一个函数指针并不意味着它马上就可以使用。和其他指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数,下面代码的说明一种指针初始化指针的一种方法:

int fun(int);
int (*pf)(int)=&f;

1.4 为啥要用函数指针?

提问❓: 函数指针为什么存在,函数指针有什么作用呢,为什么不直接调用函数而要使用函数指针??如果只是调用的话,直接写函数不是更好?
回答📕: 你不会每天都使用函数指针,但是它确实有用户之处,函数指针的用途:

  • 🎈转化表(jump table)
  • 🎈作为参数传递给另一个函数(回调)

二、函数指针作用一:回调函数

2.1回调函数的引例

参考《C和指针》第13章
这里有个简单的函数,他的作用在一个单链表中查找一个值。它的参数是一个指向链表第一个节点的指针,以及那个需要查找的值。

Node* search_list(Node *node,const int value)//首元节点+查找的值
{
	while(node!=NULL)
	{
		if(node->value==value)
		{
		break;
		}
		node=node->next;
	}
	return node;
}

这个函数看上去相当简单,但是它只适用于值为整数的链表,如果你需要在一个字符串链表中查找,你不得不另外编写一个函数。这个函数和上面那个函数绝大部分代码相同,只是第2个参数的类型,以及节点值的比较方法不同。
一种更为通用的方法是使查找函数与类型无关,这样它就能用于任何类型的值的链表。我们必须对函数的两个方面进行修改,使得它与类型无关。首先,我们要改变比较方式,这样函数这样函数就可以对任何类型的值精选比较,听上去挺难实现的,其实函数指针就可以实现。调用者编写一个函数,用于比较两个值,然后把一个指向这个函数的指针作为参数传递给查找函数。然后查找函数调用这个函数来执行值的比较。
我们必须修改的第2个方面是:向函数传递一个指向值的指针而不是值本身。函数由一个void* 形参,用于接收这个参数,然后指向这个值的指针便传递给比较函数。这个修改使字符串和数组对象也可以被使用。字符串和数组无法作为参数传递给函数,但是指向它们的指针却可以。
在这里插入图片描述
我们无法在这个环境中为回调函数编写一个准确的原型,因为我们并不知道进行比较的值的类型,事实上,我们需要查找函数能够作用于任何类型的值,解决这个难题的方法是把参数类型声明为void*,表示“一个指向未知类型的指针”。

Node* search_list(Node* node,void const *value,int (*compare)(void const*,void const* ))
{
		while(node!=NULL)
		{
			if(compare(&node->value,value)==0)
			{
				break;
			}
			node=node->link;
		}
		return node;
}

在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并把指向该函数的指针和指向需要查找的值的指针传递给查找函数。例如:下面是一个比较函数,它用于在一个整数链表中进行查找。

int compare_int ( void const *a, void const *b ){
	if* ( int * )a == * ( int * )b )
		return 0;
	else
		return 1 ;
}

用法:

desired_node = search_list ( root,&desired_value,
compare_ints );

注意强制类型转换:比较函数的参数必须声明为void *以匹配查找函数的原型,然后它们再强制转换为int *类型,用于比较整型值。
如果你希望在一个字符串链表中进行查找,下面的代码可以完成这项任务:

#include <string . h>
desired_node = search_list ( root, "desired_value " ,strcmp ) ;

碰巧,库函数strcmp所执行的比较和我们需要的完全一样,不过有些编译器会发出警告信息因为它的参数被声明为char*而不是 void *

2.2 回调函数的概念

参考bibi职坐标

  • 🎈 回调函数就是通过一个函数指针调用的函数
  • 🎈 把函数指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们说这就是回调函数
  • 🎈回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时,由另外的一方调用,用户对该事件或条件进行响应

2.3 回调函数的实现流程

  • 🎈定义一个回调函数
  • 🎈提供函数调用的另一方在初始化的时候,将回调函数的函数指针传递给调用者
  • 🎈当特定的事情或条件发生的时候,调用者使用函数指针调用所指的回调函数对事件进行处理

2.4 简单实例

#include<stdlib.h>
#include<iostream>
using namespace std; 
char process(int score, char(*p)(int))//(int类型的参数,函数指针)
{
	char reslut = p(score);//通过函数指针,调用指针所指向的get_grade(int score)
	return reslut;
}
char get_grade(int score)
{
	if (score >= 90)
		return 'A';
	else if (score >= 80)
		return 'B';
	else if (score >= 70)
		return 'C';
	else if (score >= 60)
		return 'D';
	else 
		return 'N';
}
int main()
{
	int score=0;
	printf_s("请输入成绩:");
	scanf_s("%d", &score);
	get_grade(score);
	//回调函数
	printf_s("你的等级是:%c\n", process(score, get_grade));

	return 0;
}

三、函数指针作用二:转移表

转移表就是一个函数指针数组。通过一个实例来认知转移表,现在我们要写一个小型计算器:

#include<stdlib.h>
#include<iostream>
using namespace std;

enum OPER
{
	ADD=1,SUB,MUL,DIV
};

int oper_add(int a, int b)
{
	return a + b;
}
int oper_sub(int a, int b)
{
	return a-b;
}
int oper_mul(int a, int b)
{
	return a*b;
}
int oper_div(int a, int b)
{
	return a/b;
}

int Compute(int num1,int num2,int oper)
{
	int result = 0;
	switch (oper)
	{
	case ADD:
		result = oper_add(num1, num2);
		break;
	case SUB:
		result = oper_sub(num1, num2);
		break;
	case MUL:
		result = oper_mul(num1, num2);
		break;
	case DIV:
		result = oper_div(num1, num2);
		break;

	default:
		break;
	}
	cout << "计算结果是:" << result << endl;
	return result;
}

int main()
{
	cout << "mini计算器(只支持两位数)" << endl;
	
	cout << "请输出计算数字:" << endl;
	int num1 = 0, num2 = 0;
	cin >> num1 >> num2;

	cout << "请输出操作符:" << endl;
	cout << "ADD 1,SUB 2,MUL 3,DIV 4" << endl;
	int oper;
	cin >> oper;

	Compute(num1, num2,oper);
	return 0;
}

随着功能的扩展:我们可能设计很多操作,比如:%,^,!,&&,|等等操作。对于一个新奇的具有上百个操作符的计算器,这条switch语句将会非常之长。那么我们的代码量就会极高,本着少重复好维护的原则引入:转移表 即可。

#include<stdlib.h>
#include<iostream>
using namespace std;
enum OPER
{
	ADD=1,SUB,MUL,DIV
};
int oper_add(int a, int b)
{
	return a + b;
}
int oper_sub(int a, int b)
{
	return a-b;
}
int oper_mul(int a, int b)
{
	return a*b;
}
int oper_div(int a, int b)
{
	return a/b;
}
//函数指针数组
int(*opr[])(int a, int b) = { oper_add ,oper_sub ,oper_mul ,oper_div };
int main()
{
	cout << "mini计算器(只支持两位数)" << endl;
	
	cout << "请输出计算数字:" << endl;
	int num1 = 0, num2 = 0;
	cin >> num1 >> num2;

	cout << "请输出操作符:" << endl;
	cout << "ADD 1,SUB 2,MUL 3,DIV 4" << endl;
	int oper;
	cin >> oper;

	cout << "结果是:" << opr[oper-1](num1, num2) << endl;
	return 0;
}

四、 实例——结合《C和指针》

该实例由以前读书的时候的实验报告改编,其中可能包含一些无关的函数操作:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include<string.h>
#include<iostream>
using namespace std;
typedef double ElemType;
typedef struct Node {
	ElemType data;
	struct Node* next;
}Lnode, * LinkList;
/*链表的一些基础操作 */
LinkList create(LinkList L, int n)//创建具有n个数据元素的单链表,数据元素类型为整型
{
	printf("请构造元素:\n");
	Lnode* p, * q;
	int i;
	L = (Lnode*)malloc(sizeof(Lnode));//生成头结点
	L->next = NULL;
	q = L;
	for (i = 0; i < n; i++) {
		p = (LinkList)malloc(sizeof(Lnode));//产生新结点
		cin >> p->data;
		q->next = p;
		p->next = NULL;
		q = p;
	}
	return L;
}
int length(Lnode* L)//求单链表长度
{
	Lnode* p;
	p = L;
	int size = 0;
	while (p->next != NULL)
	{
		p = p->next;//移动指针
		size++;//长度加1
	}
	return size;
}
Lnode* get(Lnode* L, int i)//寻找第i个元素,返回其指针
{
	Lnode* p; int j;
	p = L;
	j = 0;
	while (p->next != NULL && j < i)
	{
		j++;
		p = p->next;
	}
	if (i == j) return p;
	else return NULL;
}
void insert(LinkList& L, int i, ElemType x)//在第i个元素前插入一个新的元素x
{
	Lnode* s, * q;
	s = get(L, i - 1);//i前的结点
	if ((s == NULL))
	{
		printf("Insertion location invalid\n");
		return;
	}
	else
	{
		q = (Lnode*)malloc(sizeof(Lnode));//需要插入的结点
		q->data = x;//读入元素x的值
		q->next = s->next;
		s->next = q;//修改指针,实现插入操作
		return;
	}
}
Lnode* locate(Lnode* L, ElemType x)//寻找值为x的元素,返回其指针
{
	Lnode* p;
	p = L->next;
	while (p != NULL)
		if (p->data == x)
		{
			break;
		}//若p指向的结点值为x,结束循环
		else
			p = p->next;
	return p;
}
LinkList deleteElem(Lnode* L, ElemType x)//删除值为x的元素
{
	Lnode* pre = L;
	Lnode* s = L->next;
	while (s != NULL)
	{
		LinkList t = s->next;
		if (s->data == x)
		{
			pre->next = t;
			free(s);
			s = nullptr;
			break;
		}
		s = t;
		pre = pre->next;
	}
	return L;
}
void display(LinkList L)//显示线性表的数据元素
{
	printf("链表具备元素:\n");
	Lnode* p;
	p = L->next;
	while (p != NULL)//当单链表非空
	{
		cout << p->data << endl;
		p = p->next;//指向下一个结点
	}
	printf("\n");
	return;
}
Lnode* merge(Lnode* La, Lnode* Lb)//选做:合并两个有序表为一个有序表
{
	LinkList LC = (LinkList)malloc(sizeof(Lnode));
	LinkList t = LC;
	LinkList LA = La->next;
	LinkList LB = Lb->next;
	while (LA != NULL && LB != NULL)
	{
		if (LA->data > LB->data)
		{
			t->next = LB;
			LB = LB->next;
		}
		else
		{
			t->next = LA;
			LA = LA->next;
		}
		t = t->next;
	}
	if (LA != NULL)
	{
		t->next = LA;
	}
	if (LB != NULL)
	{
		t->next = LB;
	}
	return LC;
}


/* 回调函数的Demo*/
LinkList SearchNode(LinkList List, const int value)
{

	while (nullptr != List->next)
	{
		if (List->next->data == value)
		{
			break;
		}
		List = List->next;
	}
	return List;
}
bool compare_int(const void* a,const void*b)
{
	int num1 = *(int*)a;
	int num2 = *(int*)b;
	if (num1 == num2)
	{
		return true;
	}
	else
	{
		return false;
	}
}
bool compare_double(const void* a, const void* b)
{
	double num1 = *(double*)a;
	double num2 = *(double*)b;
	if ( num1==num2 )
	{
		return true;
	}
	else
	{
		return false;
	}
}

//查找元素函数指针写法
LinkList SearchList_FunPtr(LinkList List, const void* value ,bool (*compare)(const void * , const void *))
{
	while (nullptr!=List->next)
	{
		if (true== compare(&List->next->data, value))
		{
			break;
		}
		List = List->next;
	}
	return List;
}
int main()
{
	LinkList L = nullptr, La = nullptr, Lb = nullptr, Lc = nullptr;
	int num = 0;
	int mark, size = 0;
	ElemType a;
	ElemType x;

	printf("请输入构造元素个数:\n");
	scanf_s("%d", &num);
	L = create(L, num);
	display(L);

	int Serach_value=0;
	printf("请输入需要查找的元素:\n");
	scanf_s("%d", &Serach_value);
	La = SearchNode(L,Serach_value);
	display(La);

	int Serach_value_back_int = 0;
	printf("使用回调函数,请输入需要查找的 整形 元素:\n");
	cin>>Serach_value_back_int;
	Lb = SearchList_FunPtr(L, &Serach_value_back_int, compare_int);
	display(Lb);

	double Serach_value_back_double = 0;
	printf("使用回调函数,请输入需要查找的 浮点型 元素:\n");
	cin>>Serach_value_back_double;
	Lc = SearchList_FunPtr(L, &Serach_value_back_double, compare_double);
	display(Lc);

	return 0;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

愿天堂没有C++

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

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

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

打赏作者

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

抵扣说明:

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

余额充值