函数指针与回调函数详解

简介: 整形指针是指向整形的指针字符指针是指向字符的指针数组指针是指向数组的指针所以函数指针就是指向函数的指针

1.函数指针


前面我们学的:


整形指针是指向整形的指针

字符指针是指向字符的指针

数组指针是指向数组的指针

所以函数指针就是指向函数的指针


假如有一个int类型变量a,要取它的地址就是&a,有一个字符类型变量c,要取它的地址就是&c,那么一个函数的地址是怎样取到的呢。


接下来,我们取一下一个函数的地址


#include<stdio.h>
int sum(int x,int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &sum);
  return 0;
}


取出的地址:

2243142a34d34cafa4eae183d2af8a55.png


那么函数指针的类型怎么写呢?


以int sum(int x,int y)函数为例:

因为函数指针是一个指针,所以*需要先与指针名pf结合为(*pf),sum函数有两个int类型的参数,返回值为int

所以指向这个sum函数的函数指针为:int (*pf)(int,int)


int sum(int x,int y)
{
  return x + y;
}
int main()
{
  int (*pf)(int, int) = &sum;  //用一个函数指针接受sum函数的地址
  return 0;
}


对于一个数组,它的数组名是数组首元素的地址,对数组名取地址,会得到数组的地址

那么函数名和对函数取地址都表示这什么?

其实这两种写法没有区别,都是函数的地址,对于·1函数名去不去地址都能得到函数的地址

int (*pf)(int, int) = &sum;与int (*pf)(int, int) = sum;等价


函数指针的解引用:


对函数指针解引用,再对参数列表中传参:int ret = (*pf)(1,2),这样就是对函数指针的解引用


用int (*pf)(int, int) = sum;这样的写法时,可以理解为sum的地址赋给了指针pf,这时sum与pf其实表示一个意思,而在平常的函数调用时int ret = sum(1.2),这里直接写sum不用解引用。

所以在用函数指针的解引用时,也可以不用加*,即为int ret = pf(1,2)

并且这里的*其实为摆设,写不写或者写几个都是表达一个意思


接下来解读两个有意思的语句:


(*(void (*)())0)();
void (*signal(int , void(*)(int)))(int);
1.(*(void (*)())0)();:

分析:(void (*)()是一个函数指针,该函数指针指向一个无返回值,且无参数的函数,将(void (*)()用括号括起来后面跟着0,就是将0强制类型转换成函数指针类型,最后用*解引用强转后的函数指针,以因为参数列表中无参数,所以后面的括号中无参数。


结论:该代码是一次函数调用,首先先将代码中的0强制类型转圜为void (*)()类型的函数指针,然后解引用调用。


2.void (*signal(int , void(*)(int)))(int);:

分析:首先从名字signal开始下手,可以看出signal是一个函数,有两个参数,第一个是int类型的,第二个是函数指针,该函数指针指向一个返回值为void,参数为int的函数,为了方便看,把分析过的部分取出,剩下的部分是void(* )(int),所以signal函数的返回类型就是一个函数指针,该指针指向一个返回值为void参数为int类型的函数


这里对于signal函数返回值类型理解有些困难,为什么把里边函数名那部分取出来后剩下的部分就是返回类型呢?

这其实是C语言语法的锅,在不考虑语法错误的情况下,完全可以写成这样:

void(*)(int) signal(int,void(*)(int)),这样十分容易理解返回值类型,但是这样写语法是错误的,只是易于理解而已


但是如void (*signal(int , void(*)(int)))(int);这样写,一层层的括号让人很不容易理解,这里就可以使用typedef简化,因为signal的第二个参数和返回类型都是同一类,所以将void (*)(int)简化


typedef void(*)(int) pf_t,这样写很容易看明白就是用pf_t名替换void (*)(int),但这样写是错误的,由于语法,只能写成typedef void(*pf_t)(int),下面的函数声明就可以写成pf_t signal(int,pf_t)了


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


2.函数指针数组


函数指针数组——存放函数指针的数组


接下来我们写一个函数指针数组,函数指针数组顾名思义,就是有函数指针和指针数组演变而来的


int sum(int x,int y)//一个函数
{
  return x + y;
}
int(*p)(int,int) = &sum; //指向sum函数的一个函数指针


int(*p)(int,int),里面的*是与p结合的,此时还是一个指针,要将它改成一个数组,就应将名字先于[]结合,所以改为:int(*p[10])(int,int)


这里的int(*p[10])(int,int)就是函数指针数组


函数指针有什么用呢


例如要写一个计算器程序


#include <stdio.h>
void menu()
{
  printf("*******************************\n");
  printf("****** 1. add   2. sub    *****\n");
  printf("****** 3. mul   4. div    *****\n");
  printf("****** 0. exit            *****\n");
  printf("*******************************\n");
}
int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do 
  {
  menu();
  printf("请选择:>");
  scanf("%d", &input);
  switch (input)
  {
  case 1:
    printf("请输入两个操作数:>");
    scanf("%d %d", &x, &y);
    ret = Add(x, y);
    printf("%d\n", ret);
    break;
  case 2:
    printf("请输入两个操作数:>");
    scanf("%d %d", &x, &y);
    ret = Sub(x, y);
    printf("%d\n", ret);
    break;
  case 3:
    printf("请输入两个操作数:>");
    scanf("%d %d", &x, &y);
    ret = Mul(x, y);
    printf("%d\n", ret);
    break;
  case 4:
    printf("请输入两个操作数:>");
    scanf("%d %d", &x, &y);
    ret = Div(x, y);
    printf("%d\n", ret);
    break;
  case 0:
    printf("退出计算器\n");
    break;
  default:
    printf("选择错误\n");
    break;
  }
  } while (input);
  return 0;
}



从上面的程序可以看出:case语句比较长,如果有更多的计算函数,则case语句还会变长

其实可以用函数指针数组简化


因为前面的加减乘除函数的返回值一致,参数列表一样,所以完全可以将这四个函数的地址存到一个函数指针数组里


void menu()
{
  printf("*******************************\n");
  printf("****** 1. add   2. sub    *****\n");
  printf("****** 3. mul   4. div    *****\n");
  printf("****** 0. exit            *****\n");
  printf("*******************************\n");
}
int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
  do
  {
  menu();
  printf("请选择:>");
  scanf("%d", &input);
  if (input == 0)
  {
    printf("退出计算器\n");
    break;
  }
  else if (input>0&&input<=4)
  {
    printf("请输入两个操作数:>");
    scanf("%d %d", &x, &y);
    ret = p[input](x, y);
    printf("%d\n", ret);
  }
  else
  {
    printf("选择错误\n");
  }
  } while (input);
  return 0;
}


这里最主要的代码就是int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };将四个函数的地址存到一个数组里


用户会在菜单界面后输入一个整形数字,这个整形数字是用来供用户选择功能的,同时也可以称为函数指针函数的下标,通过下标取出数组中的函数指针,进而调用相应的函数。


这样写就一定程度上使代码精短


通过这个示例,这么写函数指针数组有一种跳转的感觉,给一个下标,就能通过数组下标访问到函数的地址,再去调用函数

所以像int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };这样的叫做转移表


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


前面学习了指向数组的指针


int arr[10];
int(*pa)[10] = &arr;

1

2

那么将函数指针数组的地址取出来,放到上什么类型的指针变量里呢?


这个类型是可以通过函数指针推出来的:


int (*pf[5])(int,int);这是一个函数指针数组,这里的名是先和[]结合的,所以是数组,想要变成指针,就要是名先与*结合,所以就得出:int (*(*ppf)[5])(int,int)


//ppf是指向函数指针数组的指针
int (*(*ppf)[5])(int,int) = &pf


4.回调函数


回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个

函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数

的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进

行响应。


在上面的模拟计算器程序里,由于运用case语句,其中有大量重复的语句




97badd5c2c104197961d0c9f4b436a42.png



所以就可以使用回调函数的方法去解决这个问题:


我们发现,在各个case语句中,只有调用函数语句不同,其他语句都相同。所以先新建一个函数,将那些重复语句都封装在这个函数里,我们可以通过函数传参将不同的函数指针传过去



void cale(int (*p)(int,int)) //函数参数为一个函数指针
{
  int x = 0,y = 0;
  int ret = 0;
  int input = 0;
  printf("请输入两个操作数:>");
  scanf("%d %d", &x, &y);
  ret = p(x, y);
  printf("%d\n", ret);
}
int main()
{
  int input = 0;
  do 
  {
  menu();
  printf("请选择:>");
  scanf("%d", &input);
  switch (input)
  {
  case 1:
    calc(Add);
    break;
  case 2:
    calc(Sub);
    break;
  case 3:
    calc(Mul);
    break;
  case 4:
    calc(Div);
    break;
  case 0:
    printf("退出计算器\n");
    break;
  default:
    printf("选择错误\n");
    break;
  }
  } while (input);
  return 0;
}



这样写就可以将case语句中的语句做到最简,想要使用哪个功能就将哪个函数的指针传过去


通过函数指针调用的函数是回调函数,所以在这个程序里Add,Sub,Mul,div是回调函数


还有一个回调函数的应用是qsort函数,具体的内容在另一篇文章中:点击跳转


目录
相关文章
|
7月前
|
Unix
网络编程之 信号捕捉器(函数指针与回调函数)(2)
sigaction()函数 前面我们讲到的内容已经足以用来防止僵尸进程生成的代码。之所以博主还要介绍sigaction()函数是因为它类似于signal()函数,而且完全可以代替后者,也更稳定(主要是书上介绍到了
77 1
|
7月前
|
Linux
网络编程之 信号捕捉器(函数指针与回调函数)(1)
接着我们的信号说下去 之前博主给大家分享到了信号的概念和初步介绍signal函数的形式后就没有继续往下介绍了,实在是因为时间不够,那个时候博主还要上课,现在博主放假了就好好给大家分享一下如何注册信号捕捉,以及信号捕捉器的妙用。
75 1
|
2月前
|
C++
指针中的回调函数与qsort的深度理解与模拟
本文详细介绍了回调函数的概念及其在计算器简化中的应用,以及C++标准库函数qsort的原理和使用示例,包括冒泡排序的模拟实现。
26 1
|
7月前
指针(5)---回调函数
指针(5)---回调函数
32 0
|
2月前
魔法指针 之 函数指针 回调函数
魔法指针 之 函数指针 回调函数
22 0
|
7月前
|
编译器 C语言
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(下)
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。
58 0
|
6月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
7月前
|
算法 搜索推荐 C语言
c函数指针与回调函数
c函数指针与回调函数
52 2
|
7月前
|
算法 搜索推荐 程序员
C语言中的函数指针和回调函数
C语言中的函数指针和回调函数
50 2
|
7月前
|
存储 C语言
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(中)
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。
47 0