一. 函数指针
关于函数指针的概念,可以想到一个整型指针指向的是一个整型,它的值是所指向对象的地址;一个字符串指针指向的是一个字符串,它的值是所指向字符串的首地址;因此,一个函数指针当然是一个指针变量了,它所指向的是一个函数,它的值就是所指向函数的入口地址。
函数指针的定义如下:
typedef int data_type; data_type (*pfun)(data_type, data_type);
上面的语句中定义了一个函数指针pfun,它表示指向一个返回值为data_type,参数为两个data_type类型的函数,上面第一个括号也就是(*pfun)的括号不能省略,否则就会变成:
data_type *pfun(data_type, data_type);
这样的话就为声明一个函数名为pfun的函数了,它的返回值为data_type*,参数为两个data_type类型的参数;
下面举个栗子说明函数指针的使用:
#include <iostream> using namespace std; typedef int data_type; data_type add(data_type& a, data_type& b) { return (a + b); } int main() { data_type a = 2; data_type b = 3; data_type (*pfun)(data_type&, data_type&); pfun = &add; cout<<pfun(a, b)<<endl; return 0; }
上面的程序中实现了一个函数add,并且定义了一个函数指针pfun指向这个函数,对函数指针的赋值和使用其他指针赋值语句一样,可以取函数的地址直接赋过去,但是因为函数名作为函数的入口地址,因此也可以不加取地址操作符“&”而直接将函数名赋给函数指针;要注意的是,函数指针的定义中,参数类型、个数和返回值必须和要指向的函数原型中的参数类型、个数和返回值一一对应;
运行程序会得到结果5;
指针在C/C++中是一个很灵活的变量,它可以指向与自己类型相同的不同存储空间,比如在数组中通常可以用指针来操纵,但这里值得注意的是,在一个数组或者字符串中使用指针进行加减操作会进行相应的移位指向下一个空间,有整型指针数组也就自然会有函数指针数组,它的数组成员都是一个个指向某个函数的函数指针,而在这样的数组中用指针进行加减操作就指向的是不同的函数指针也就是不同的函数了,单纯的对一个函数指针进行加减操作是不能够的,它并不会指向在当前函数上面或下面定义的某个函数。
像给一个整型指针赋值一样,可以给一个函数指针赋予不同的函数,这样就可以灵活的用一个指针来调用不同的函数而不用将每个函数都显式的写出来。
二. 回调函数
上面谈论的函数指针其实就是在为谈回调函数做铺垫,什么是回调函数?其实回调函数就是函数指针的一种使用,用户自己定义一个函数,将这个指向这个函数的函数指针作为参数传递给一个系统函数或者中间函数,当这个系统函数或中间函数执行的时候调用这个函数指针去执行用户定义的函数,那么用户定义的这个函数就叫做回调函数。
为什么会有回调函数呢?难道就不能在一个函数里面直接调用用户所写的函数而不是传参过去吗?这种直接使用被调用函数的用法是在我们知道调用函数的内部实现机制的情况下直接写入的,那么,如果调用函数是系统内部函数或者是别人所给的一个函数借口呢?再如果有一种设计需求,要求执行一个函数但并不知道调用该函数的函数是如何操纵的呢?这样就没办法直接在调用函数内部写入被调用函数了,而是需要传入一个函数地址,至于该函数是如何调用如何来实现的,我们并不需要关心。
从上面的分析来看,回调函数的使用并不是你传给我,我调用你,而是还需要有一个起始的函数来调用系统函数或者中间函数将回调函数的地址作为参数给传过去,可画图说明:
图中的虚线,如果中间函数是系统函数,首先会由起始函数调用系统函数而由用户态进入内核态去由执行操作系统的函数,然后系统函数内部会执行调用用户实现的一个callback函数而从内核态再返回到用户态去执行调用callback函数,我个人认为也可以这么理解回调函数的回调二字,因此虚线是用户态和内核态的一个划分;但如果中间函数并不是系统函数,那么就一直会在用户态而不会接触到系统内部。
栗子时间:
#include <iostream> using namespace std; void print() { //代码 cout<<"hello..."<<endl; //代码 } void say_hello(void (*pfun)(void)) { //代码 pfun(); //代码 } int main() { //代码 say_hello(print); //代码 return 0; }
上面就是一个再简单不过的小栗子,注释掉的代码就可以是用户自己其他的实现,而如果栗子中的say_hello函数是系统函数或者是别人传过来的一个函数接口的话,其内部实现我们是无法干涉和了解的,因此,只需要将我们希望执行的回调函数地址给传过去,从而完成我们需要回调函数来完成的任务就可以了。
《完》