@[toc]
一、函数指针
参考: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章
这里有个简单的函数,他的作用在一个单链表中查找一个值。它的参数是一个指向链表第一个节点的指针,以及那个需要查找的值。
这个函数看上去相当简单,但是它只适用于值为整数的链表,如果你需要在一个字符串链表中查找,你不得不另外编写一个函数。这个函数和上面那个函数绝大部分代码相同,只是第2个参数的类型,以及节点值的比较方法不同。Node* search_list(Node *node,const int value)//首元节点+查找的值 { while(node!=NULL) { if(node->value==value) { break; } node=node->next; } return node; }
一种更为通用的方法是使查找函数与类型无关,这样它就能用于任何类型的值的链表。我们必须对函数的两个方面进行修改,使得它与类型无关。首先,我们要改变比较方式,这样函数这样函数就可以对任何类型的值精选比较,听上去挺难实现的,其实函数指针就可以实现。调用者编写一个函数,用于比较两个值,然后把一个指向这个函数的指针作为参数传递给查找函数。然后查找函数调用这个函数来执行值的比较。
我们必须修改的第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; }
三、函数指针作用二:转移表
转移表就是一个函数指针数组。通过一个实例来认知转移表,现在我们要写一个小型计算器:
```cppinclude
include
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`语句将会非常之长。那么我们的代码量就会极高,本着少重复好维护的原则引入:**转移表** 即可。
```cpp
#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;
}