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

简介: 函数指针、回调函数、转移表

@[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章
    这里有个简单的函数,他的作用在一个单链表中查找一个值。它的参数是一个指向链表第一个节点的指针,以及那个需要查找的值。
    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;
    }
    

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

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

    include

    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;
}
相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
4天前
|
Unix
网络编程之 信号捕捉器(函数指针与回调函数)(2)
sigaction()函数 前面我们讲到的内容已经足以用来防止僵尸进程生成的代码。之所以博主还要介绍sigaction()函数是因为它类似于signal()函数,而且完全可以代替后者,也更稳定(主要是书上介绍到了
38 1
|
4天前
|
Linux
网络编程之 信号捕捉器(函数指针与回调函数)(1)
接着我们的信号说下去 之前博主给大家分享到了信号的概念和初步介绍signal函数的形式后就没有继续往下介绍了,实在是因为时间不够,那个时候博主还要上课,现在博主放假了就好好给大家分享一下如何注册信号捕捉,以及信号捕捉器的妙用。
40 1
|
7月前
【C进阶】回调函数(指针进阶2,详解,小白必看)(中)
【C进阶】回调函数(指针进阶2,详解,小白必看)(中)
|
7月前
|
存储
【C进阶】回调函数(指针进阶2,详解,小白必看)(上)
【C进阶】回调函数(指针进阶2,详解,小白必看)(上)
|
7月前
|
C语言
通过模拟实现计算器介绍函数指针数组和回调函数的用法【C语言/指针/进阶】
通过模拟实现计算器介绍函数指针数组和回调函数的用法【C语言/指针/进阶】
47 0
|
7月前
|
小程序 算法
如何降低代码的冗余度(指针的妙用)——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙
如何降低代码的冗余度(指针的妙用)——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙
32 0
|
7月前
|
搜索推荐 程序员 编译器
神奇的库函数qsort【详解指向函数指针数组的指针、回调函数、模拟实现qsort函数】【C语言/指针/进阶/程序员内功修炼】【下】
神奇的库函数qsort【详解指向函数指针数组的指针、回调函数、模拟实现qsort函数】【C语言/指针/进阶/程序员内功修炼】【下】
42 0
|
4天前
|
算法 搜索推荐 程序员
C语言中的函数指针和回调函数
C语言中的函数指针和回调函数
12 2
|
4天前
|
存储 C语言
c语言函数指针和指针函数的区别,以及回调函数的使用。
c语言函数指针和指针函数的区别,以及回调函数的使用。
12 0
|
4天前
|
存储 程序员 API
C函数指针与回调函数
C函数指针与回调函数
27 0

热门文章

最新文章