回调函数,Python回调函数用法实例详解

  在讨论回调函数前我们先看一下一下两种情景。

Python回调函数用法实例详解,python回调函数实例

本文实例讲述了Python回调函数用法。分享给大家供大家参考。具体分析如下:

一、百度百科上对回调函数的解释:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

二、什么是回调:

软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在不同软件架构中的实现。

三、一个小例子:

#call.py 
import called 
def callback(): 
  print "in callback" 
def main(): 
  #called.test() 
  called.test_call(callback) 
  print "in call.py" 
main() 
#called.py 
''''' 
def test(): 
  print "in called.py test()" 
''' 
def test_call(p_call): 
  print "in called.py test_call()" 
  p_call() 
[email protected]:~/test/python$ python call.py 
in called.py test_call() 
in callback 
in call.py 
[email protected]:~/test/python$

网上搜到的一个面向对象实现的例子:

当你要加入回调(Callback)功能的时候,代码往往会偏重于回调的实现而不是问题本身了。一个解决方法就是实现一个通用的基础类来解决回调的需求,然后再来实现你为某个事件(Event)所绑定(Binding)的方法(Method)。

代码如下:

class CallbackBase: 
  def __init__(self): 
 self.__callbackMap = {} 
 for k in (getattr(self, x) for x in dir(self)): 
   if hasattr(k, "bind_to_event"): 
 self.__callbackMap.setdefault(k.bind_to_event, []).append(k) 
   elif hasattr(k, "bind_to_event_list"): 
 for j in k.bind_to_event_list: 
   self.__callbackMap.setdefault(j, []).append(k) 
  ## staticmethod is only used to create a namespace 
  @staticmethod 
  def callback(event): 
 def f(g, ev = event): 
   g.bind_to_event = ev 
   return g 
 return f 
  @staticmethod 
  def callbacklist(eventlist): 
 def f(g, evl = eventlist): 
   g.bind_to_event_list = evl 
   return g 
 return f 
  def dispatch(self, event): 
 l = self.__callbackMap[event] 
 f = lambda *args, **kargs:  
   map(lambda x: x(*args, **kargs), l) 
 return f 
## Sample 
class MyClass(CallbackBase): 
  EVENT1 = 1 
  EVENT2 = 2 
  @CallbackBase.callback(EVENT1) 
  def handler1(self, param = None): 
 print "handler1 with param: %s" % str(param) 
 return None 
  @CallbackBase.callbacklist([EVENT1, EVENT2]) 
  def handler2(self, param = None): 
 print "handler2 with param: %s" % str(param) 
 return None 
  def run(self, event, param = None): 
 self.dispatch(event)(param) 
if __name__ == "__main__": 
  a = MyClass() 
  a.run(MyClass.EVENT1, 'mandarina') 
  a.run(MyClass.EVENT2, 'naranja') 

这里有一个类,它有两个事件(EVENT1和EVENT2)和两个处理函数(handler)。第一个处理函数handler1注册了EVENT1,而第二个处理函数handler2当EVENT1或者EVENT2发生的时候都会执行(即注册了全部的事件)。

运行函数(run)在MyClass的主循环中,它会将对应的事件派送(dispatch)出去。这(这里指dispatch函数)会返回一个函数,我们可以把所有需要传给这个函数的参数列表传给它。这个函数运行结束会返回一个列表(list),列表中是所有的返回值。

也许,使用Metaclass能够实现的更优雅一些吧。

希望本文所述对大家的Python程序设计有所帮助。

本文实例讲述了Python回调函数用法。分享给大家供大家参考。具体分析如下: 一、百度百...

转载自:

**什么是回调函数?

  1、你在敲代码,中午了,于是你去炒菜,然后敲代码。

要开始使用 Boost.Function, 就要包含头文件 "boost/function.hpp", 或者某个带数字的版本,从 "boost/function/function0.hpp" 到 "boost/function/function10.hpp". 如果你知道你想保存在 function 中的函数的参数数量,这样做可以让编译器仅包含需要的头文件。如果包含 "boost/function.hpp", 那么就会把其它的头文件也包含进去。

**  简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

  2、你在敲代码,中午了,于是你去炒菜,然后打了个电话给美团,点了份外卖,继续敲代码。

理解被存函数的最佳方法是把它想象为一个普通的函数对象,该函数对象用于封装另一个函数(或函数对象)。这个被存的函数的最大用途是它可以被多次调用,而无须在创建 function 时立即使用。在声明 function时,声明中最重要的部分是函数的签名。这部分即是告诉 function 它将保存的函数或函数对象的签名和返回类型。我们已经看到,有两种方法来执行这个声明。这里有一个完整的程序,程序声明了一个 boost::function ,它可以保存返回 bool (或某个可以隐式转换为 bool的类型)并接受两个参数的类函数实体,第一个参数可以转换为 int, 第二个参数可以转换为 double.

  **为什么要使用回调函数?

  从代码的角度看,1就是我们平时的功能函数的调用,2是调用回调函数。

#include <iostream>
#include "boost/function.hpp"

bool some_func(int i,double d) 
{
  return i>d;
}

int main() 
{
  boost::function<bool (int,double)> f;
  f=&some_func;
  f(10,1.1);
}

**  因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。

  可以看出回调函数一个非常重要的好处就是你的程序变成异步了。也就是你不必再调用这个函数的时候一直等待这个时间的到达、事件的发生或中断的发生(万一一直不发生,你的程序会怎么样?),

当 function f 首次创建时,它不保存任何函数。它是空的,可以在一个布尔上下文中进行测试。如果你试图调用一个没有保存任何函数或函数对象的 function ,它将抛出一个类型 bad_function_call 的异常。为了避免这个问题,我们用普通的赋值语法把一个指向 some_func 的指针赋值给 f 。这导致 f 保存了到 some_func 的指针。最后,我们用参数10 (一个 int) 和 1.1 (一个 double)来调用 f (用函数调用操作符)。要调用一个 function, 你必须提供被存函数或函数对象所期望的准确数量的参数。

  如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、Shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。

再此期间你可以做做别的事情,或者四处逛逛。当回调函数被执行时,你的程序重新得到执行的机会,此时你可以继续做必要的事情了。

回调的基础

我们先来看看在没有 Boost.Function 以前我们如何实现一个简单的回调,然后再把代码改为使用 function, 并看看会带来什么优势。我们从一个支持某种简单的回调形式的类开始,它可以向任何对新值关注的对象报告值的改变。这里的回调是一种传统的C风格回调,即使用普通函数。这种回调用可用于象GUI控制这样的场合,它可以通知观察者用户改变了它的值,而不需要对监听该信息的客户有任何特殊的知识。

#include <iostream>
#include <vector>
#include <algorithm>
#include "boost/function.hpp"

void print_new_value(int i) 
{
  std::cout << "The value has been updated and is now " << i << '/n';
}

void interested_in_the_change(int i)
{
  std::cout << "Ah, the value has changed./n";
}

class notifier 
{
  typedef void (*function_type)(int);
  std::vector<function_type> vec_;
  int value_;
public:
  void add_observer(function_type t)
  {
    vec_.push_back(t);
  }

  void change_value(int i) 
 {
    value_=i;
    for (std::size_t i=0;i<vec_.size();  i) 
  {
      (*vec_[i])(value_);
    }
  }
};

int main() 
{
  notifier n;
  n.add_observer(&print_new_value);
  n.add_observer(&interested_in_the_change);

  n.change_value(42);
}

这里的两个函数,print_new_value 和 interested_in_the_change, 它们的函数签名都兼容于 notifier 类的要求。这些函数指针被保存在一个 vector 内,并且无论何时它的值被改变,这些函数都会在一个循环里被调用。调用这些函数的一种语法是:

(*vec_[i])(value_);

值(value_)被传递给解引用的函数指针(即 vec_[i] 所返回的)。另一种写法也是有效的,即这样:

vec_[i](value_);

这种写法看起来更好看些,但更为重要的是,它还可以允许你把函数指针更换为 Boost.Function 而没有改变调用的语法。现在,工作还是正常的,但是,唉,函数对象不能用于这个 notifier 类。事实上,除了函数指针以外,别的任何东西都不能用,这的确是一种局限。但是,如果我们使用 Boost.Function,它就可以工作。重写这个 notifier 类非常容易。

class notifier 
{
  typedef boost::function<void(int)> function_type;
  std::vector<function_type> vec_;
  int value_;
public:
 template <typename T> void add_observer(T t)
 {
    vec_.push_back(function_type(t));
  }

  void change_value(int i) 
  {
    value_=i;
    for (std::size_t i=0;i<vec_.size();  i) 
    {
      vec_[i](value_);
    }
  }
};

首先要做的事是,把 typedef 改为代表 boost::function 而不是函数指针。之前,我们定义的是一个函数指针;现在,我们使用泛型方法,很快就会看到它的用途。接着,我们把成员函数 add_observer 的签名改为泛化的参数类型。我们也可以把它改为接受一个 boost::function,但那样会要求该类的用户必须也知道 function 的使用方法[2],而不是仅仅知道这个观察者类型的要求就行了。应该注意到 add_observer 的这种变化并不应该是转向 function 的结果;无论如何代码应该可以继续工作。我们把它改为泛型的;现在,不管是函数指针、函数对象,还是 boost::function 实例都可以被传递给 add_observer, 而无须对已有用户代码进行任何改动。把元素加入到 vector 的代码有一些修改,现在需要创建一个 boost::function<void(int)> 实例。最后,我们把调用这些函数的语法改为可以使用函数、函数对象以及 boost::function 实例[3]。这种对不同类型的类似函数的"东西"的扩展支持可以立即用于带状态的函数对象,它们可以实现一些用函数很难做到的事情。

如果我们不知道Boost.Function,我们需要将添加到接口上的任何东西都必须及时向用户解释清楚。

class knows_the_previous_value 
{
  int last_value_;
public:
  void operator()(int i) 
  {
    static bool first_time=true;
    if (first_time) {
      last_value_=i;
      std::cout <<  "This is the first change of value, so I don't know the previous one./n";
      first_time=false;
      return;
    }
    std::cout << "Previous value was " << last_value_ << '/n';
    last_value_=i;
  }
};

这个函数对象保存以前的值,并在值被改变时把旧值输出到 std::cout 。注意,当它第一次被调用时,它并不知道旧值。这个函数对象在函数中使用一个静态 bool 变量来检查这一点,该变量被初始化为 true. 由于函数中的静态变量是在函数第一次被调用时进行初始化的,所以它仅在第一次调用时被设为 true 。虽然也可以在普通函数中使用静态变量来提供状态,但是我们必须知道那样不太好,而且很难做到多线程安全。因此,带状态的函数对象总是优于带静态变量的普通函数。notifier 类并不关心这是不是函数对象,只要符合要求就可以接受。以下更新的例子示范了它如何使用。

int main() {
  notifier n;
  n.add_observer(&print_new_value);
  n.add_observer(&interested_in_the_change);
  n.add_observer(knows_the_previous_value());

  n.change_value(42);
  std::cout << '/n';
  n.change_value(30);
}

关键一点要注意的是,我们新增的一个观察者不是函数指针,而是一个 knows_the_previous_value 函数对象的实例。运行这段程序的输出如下:

The value has been updated and is now 42
Ah, the value has changed.
This is the first change of value, so I don't know the previous one.

The value has been updated and is now 30
Ah, the value has changed.
Previous value was 42

在这里最大的优点不是放宽了对函数的要求(或者说,增加了对函数对象的支持),而是我们可以使用带状态的对象,这是非常需要的。我们对 notifier 类所做的修改非常简单,而且用户代码不受影响。如上所示,把 Boost.Function 引入一个已有的设计中是非常容易的。

  回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。

  回调函数是一个不被设计者直接调用,而是被其他人回过来调用的函数

类成员函数

Boost.Function 不支持参数绑定,这在每次调用一个 function 就要调用同一个类实例的成员函数时是需要的。幸运的是,如果这个类实例被传递给 function 的话,我们就可以直接调用它的成员函数。这个 function 的签名必须包含类的类型以及成员函数的签名。换言之,显式传入的类实例要作为隐式的第一个参数,this。这样就得到了一个在给出的对象上调用成员函数的函数对象。看一下以下这个类:

class some_class 
{
public:
  void do_stuff(int i) const 
  {
    std::cout << "OK. Stuff is done. " << i << '/n';
  }
};

员函数 do_stuff 要从一个 boost::function 实例里被调用。要做到这一点,我们需要 function 接受一个 some_class实例,签名的其它部分为一个 void 返回以及一个 int 参数。对于如何把 some_class 实例传给 function,我们有三种选择:传值,传引用,或者传址。如何要传值,代码就应该这样写(很少以传值方式传递函数对象)

boost::function<void(some_class,int)> f;

注意,返回类型仍旧在最开始,后跟成员函数所在的类,最后是成员函数的参数类型。它就象传递一个 this 给一个函数,该函数暗地里用类实例调用一个非成员函数。要把函数 f 配置为成员函数 do_stuff, 然后调用它,我们这样写:

f=&some_class::do_stuff;
f(some_class(),2);

如果要传引用,我们要改一下函数的签名,并传递一个 some_class 实例

boost::function<void(some_class&,int)> f;
f=&some_class::do_stuff;
some_class s;
f(s,1);

最后,如果要传 some_class 的指针(裸指针或智能指针皆可),我们就要这样写:

boost::function<void(some_class*,int)> f;
f=&some_class::do_stuff;
some_class s;
f(&s,3);

所有这些传递"虚拟 this"实例的方法都已经在库中提供。当然,这种技术也是有限制的:你必须显式地传递类实例;而理想上,你更愿意这个实例被绑定在函数中。乍一看,这似乎是 Boost.Function 的缺点,但有别的库可以支持参数的绑定,如 Boost.Bind 和 Boost.Lambda. 我们将在本章稍后的地方示范这些库会给 Boost.Function 带有什么好处。

  另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。

  回调是一种非常重要的机制,主要用来实现软件的分层设计,使得不同软件模块的开发者的工作进度可以独立出来,不受时空的限制,需要是通过约定好的接口(或标准)相互契合在一起

带状态的函数对象

我们已经看到,由于支持了函数对象,就可以给回调函数增加状态。考虑这样一个类,keeping_state, 它是一个带状态的函数对象。keeping_state 的实例记录一个总和,它在每次调用操作符执行时被增加。现在,将该类的一个实例用于两个 boost::function 实例,结果有些出人意外。

#include <iostream>
#include "boost/function.hpp"

class keeping_state 
{
  int total_;
public:
  keeping_state():total_(0) {}

  int operator()(int i) 
  {
    total_ =i;
    return total_;
  }

  int total() const 
  {
    return total_;
  }
};

int main() 
{
  keeping_state ks;
  boost::function<int(int)> f1;
  f1=ks;

  boost::function<int(int)> f2;
  f2=ks;

  std::cout << "The current total is " << f1(10) << '/n';
  std::cout << "The current total is " << f2(10) << '/n';
  std::cout << "After adding 10 two times, the total is " 
    << ks.total() << '/n';
}

写完这段代码并接着执行它,程序员可能期望保存在 ks 的总和是20,但不是;事实上,总和为0。以下是这段程序的运行结果

The current total is 10
The current total is 10
After adding 10 two times, the total is 0

原因是每一个 function 实例(f1 和 f2)都含有一个 ks 的拷贝,这两个实例得到的总和都是10,但 ks 没有变化。这可能是也可能不是你想要的,但是记住,boost::function 的缺省行为是复制它要调用的函数对象,这一点很重要。如果这导致不正确的语义,或者如果某些函数对象的复制代价太高,你就必须把函数对象包装在 boost::reference_wrapper 中,那样 boost::function 的复制就会是一个 boost::reference_wrapper 的拷贝,它恰好持有一个到原始函数对象的引用。你无须直接使用 boost::reference_wrapper ,你可以使用另两个助手函数,ref 和 cref。 这两函数返回一个持有到某特定类型的引用或 const 引用的 reference_wrapper。在前例中,要获得我们想要的语义,即使用同一个 keeping_state 实例,我们就需要把代码修改如下:

int main() {
  keeping_state ks;
  boost::function<int(int)> f1;
  f1=boost::ref(ks);

  boost::function<int(int)> f2;
  f2=boost::ref(ks);

  std::cout << "The current total is " << f1(10) << '/n';
  std::cout << "The current total is " << f2(10) << '/n';
  std::cout << "After adding 10 two times, the total is " 
    << ks.total() << '/n';
}

boost::ref 的用途是通知 boost::function,我们想保存一个到函数对象的引用,而不是一个拷贝。运行这个程序有以下输出:

The current total is 10
The current total is 20
After adding 10 two times, the total is 20

这正是我们想要的结果。使用 boost::ref 和 boost::cref 的不同之处就象引用与 const 引用的差异,对于后者,你只能调用其中的常量成员函数。以下例子使用一个名为 something_else 的函数对象,它有一个 const 的调用操作符。

class something_else 
{
public:
  void operator()() const 
  {
    std::cout << "This works with boost::cref/n";
  }
};

对于这个函数对象,我们可以使用 boost::ref 或 boost::cref.

something_else s;
boost::function0<void> f1;
f1=boost::ref(s);
f1();
boost::function0<void> f2;
f2=boost::cref(s);
f2();

如果我们改变了 something_else 的实现,使其函数为非const, 则只有 boost::ref 可以使用,而 boost::cref 将导致一个编译期错误。

class something_else 
{
public:
  void operator()() 
  {
    std::cout << 
      "This works only with boost::ref, or copies/n";
  }
};

something_else s;
boost::function0<void> f1;
f1=boost::ref(s); // This still works
f1(); 
boost::function0<void> f2;
f2=boost::cref(s); // This doesn't work; 
                   // the function call operator is not const
f2();

如果一个 function 包含一个被 boost::reference_wrapper 所包装的函数对象,那么复制构造函数与赋值操作就会复制该引用,即 function 的拷贝将引向原先的函数对象。

int main()
 {
  keeping_state ks;
  boost::function1<int,int> f1;  // 译注:原文为boost::function<int,int> f1,有误
  f1=boost::ref(ks);

  boost::function1<int,int> f2(f1);  // 译注:原文为boost::function<int,int> f2(f1),有误 
  boost::function1<short,short> f3;  // 译注:原文为boost::function<short,short> f3,有误 
  f3=f1;

  std::cout << "The current total is " << f1(10) << '/n';
  std::cout << "The current total is " << f2(10) << '/n';
  std::cout << "The current total is " << f3(10) << '/n';
  std::cout << "After adding 10 three times, the total is " 
    << ks.total() << '/n';
}

这等同于使用 boost::ref 并把函数对象 ks 赋给每一个 function 实例。给回调函数增加状态,可以发挥巨大的能力,这也正是使用 Boost.Function 与使用函数对象相比具有的非常突出的优点。

  不管怎么说,回调函数是继续自C语言的,因而,在C 中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C 中应使用虚拟方法或函数符(functor),而不是回调函数。

  如何可以实现回调机制:

与 Boost.Function 一起使用 Boost.Bind  

当我们把 Boost.Function 与某个支持参数绑定的库结合起来使用时,事情变得更为有趣。Boost.Bind 为普通函数、成员函数以及成员变量提供参数绑定。这非常适合于 Boost.Function, 我们常常需要这类绑定,由于我们使用的类本身并不是函数对象。那么,我们用 Boost.Bind 把它们转变为函数对象,然后我们可以用 Boost.Function 来保存它们并稍后调用。在将图形用户界面(GUIs)与如何响应用户的操作进行分离时,几乎总是要使用某种回调方法。如果这种回调机制是基于函数指针的,就很难避免对可以使用回调的类型的某些限制,也就增加了界面表现与业务逻辑之间的耦合风险。通过使用 Boost.Function,我们可以避免这些事情,并且当与某个支持参数绑定的库结合使用时,我们可以轻而易举地把上下文提供给调用的函数。这是本库最常见的用途之一,把业务逻辑即从表示层分离出来。

以下例子包含一个艺术级的磁带录音机,定义如下:

class tape_recorder 
{
public:
  void play() 
  {
    std::cout << "Since my baby left me.../n";
  }

  void stop()
  {
    std::cout << "OK, taking a break/n";
  }

  void forward() 
  {
    std::cout << "whizzz/n";
  }

  void rewind() 
  {
    std::cout << "zzzihw/n";
  }

  void record(const std::string& sound)
  {
    std::cout << "Recorded: " << sound << '/n';
  }
};

这个磁带录音机可以从一个GUI进行控制,或者也可能从一个脚本客户端进行控制,或者从别的源进行控制,这意味着我们不想把这些函数的执行与它们的实现耦合起来。建立这种分离的一个常用的方法是,用专门的对象负责执行命令,而让客户对命令如何执行毫无所知。这也被称为命令模式(Command pattern),并且在它非常有用。这种模式的特定实现中的一个问题是,需要为每个命令创建单独的类。以下片断示范了它看起来是个什么样子:

class command_base 
{
public:
  virtual bool enabled() const=0;
  virtual void execute()=0;

  virtual ~command_base() {}
};

class play_command : public command_base 
{
  tape_recorder* p_;
public:
  play_command(tape_recorder* p):p_(p) {}

  bool enabled() const 
  {
    return true;
  }

  void execute() 
  {
    p_->play();
  }
};

class stop_command : public command_base 
{
  tape_recorder* p_;
public:
  stop_command(tape_recorder* p):p_(p) {}

  bool enabled() const 
  {
    return true;
  }

  void execute() 
  {
    p_->stop();
  }
};

这并不是一个非常吸引的方案,因为它使得代码膨胀,有许多简单的命令类,而它们只是简单地负责调用一个对象的单个成员函数。有时候,这是必需的,因为这些命令可能需要实现业务逻辑和调用函数,但通常它只是由于我们所使用的工具有所限制而已。这些命令类可以这样使用:

int main() {
  tape_recorder tr;

  // 使用命令模式
  command_base* pPlay=new play_command(&tr);
  command_base* pStop=new stop_command(&tr);

  // 在按下某个按钮时调用
  pPlay->execute();
  pStop->execute();

  delete pPlay;
  delete pStop;
}

现在,不用再创建额外的具体的命令类,如果我们实现的命令都是调用一个返回 void 且没有参数(先暂时忽略函数 record, 它带有一个参数)的成员函数的话,我们可以来点泛化。不用再创建一组具体的命令,我们可以在类中保存一个指向正确成员函数的指针。这是迈向正确方向(虽然随时了一点效率)的一大步,就象这样:

class tape_recorder_command : public command_base 
{
  void (tape_recorder::*func_)(); 
  tape_recorder* p_;
public:

  tape_recorder_command(
    tape_recorder* p,
    void (tape_recorder::*func)()) : p_(p),func_(func) {}

  bool enabled() const 
  {
    return true;
  }

  void execute() 
  {
    (p_->*func_)();
  }
};

这个命令模式的实现要好多了,因为它不需要我们再创建一组完成相同事情的独立的类。这里的不同在于我们保存了一个 tape_recorder 成员函数指针在 func_ 中,它要在构造函数中提供。命令的执行部分可能并不是你要展现给你的朋友看的东西,因为成员指针操作符对于一些人来说可能还不太熟悉。但是,这可以被看为一个低层的实现细节,所以还算好。有了这个类,我们可以进行泛化处理,不再需要实现单独的命令类。

  tape_recorder tr;

  // 使用改进的命令模式
  command_base* pPlay=
    new tape_recorder_command(&tr,&tape_recorder::play);
  command_base* pStop=
    new tape_recorder_command(&tr,&tape_recorder::stop);

  // 从一个GUI或一个脚本客户端进行调用
  pPlay->execute();
  pStop->execute();

  delete pPlay;
  delete pStop;
}

你可能还没有理解,我们已经在开始实现一个简单的 boost::function 版本,它已经可以做到我们想要的。不要重复发明轮子,让我们重点关注手边的工作:分离调用与实现。以下是一个全新实现的 command 类,它更容易编写、维护以及理解。

class command {
  boost::function<void()> f_;
public:
  command() {}
  command(boost::function<void()> f):f_(f) {}

  void execute() {
    if (f_) {
      f_();
    }
  }

  template <typename Func> void set_function(Func f) {
    f_=f;
  }

  bool enabled() const {
    return f_;
  }
};

通过使用 Boost.Function,我们可以立即从同时兼容函数和函数对象——包括由绑定器生成的函数对象——的灵活性之中获益。这个 command 类把函数保存在一个返回 void 且不接受参数的 boost::function 中。为了让这个类更加灵活,我们提供了在运行期修改函数对象的方法,使用一个泛型的成员函数,set_function.

template <typename Func> void set_function(Func f) 
{
  f_=f;
}

通过使用泛型方法,任何函数、函数对象,或者绑定器都兼容于我们的 command 类。我们也可以选择把 boost:: function作为参数,并使用 function 的转型构造函数来达到同样的效果。这个 command 类非常通用,我们可以把它用于我们的 tape_recorder 类或者别的地方。与前面的使用一个基类与多个具体派生类(在那里我们使用指针来实现多态的行为)的方法相比,还有一个额外的优点就是,它更容易管理生存期问题,我们不再需要删除命令对象,它们可以按值传递和保存。我们在布尔上下文中使用 function f_ 来测试命令是否可用。如果函数不包含一个目标,即一个函数或函数对象,它将返回 false, 这意味着我们不能调用它。这个测试在 execute 的实现中进行。以下是使用我们这个新类的一个例子:

int main() {
  tape_recorder tr;

  command play(boost::bind(&tape_recorder::play,&tr));
  command stop(boost::bind(&tape_recorder::stop,&tr));
  command forward(boost::bind(&tape_recorder::stop,&tr));
  command rewind(boost::bind(&tape_recorder::rewind,&tr));
  command record;

  // 从某些GUI控制中调用...
  if (play.enabled()) {
    play.execute();
  }

  // 从某些脚本客户端调用...
  stop.execute();

  // Some inspired songwriter has passed some lyrics
  std::string s="What a beautiful morning...";
  record.set_function(
    boost::bind(&tape_recorder::record,&tr,s));
  record.execute();
}

为了创建一个具体的命令,我们使用 Boost.Bind 来创建函数对象,当通过这些对象的调用操作符进行调用时,就会调用正确的 tape_recorder 成员函数。这些函数对象是自完备的;它们无参函数对象,即它们可以直接调用,无须传入参数,这正是 boost::function<void()> 所表示的。换言之,以下代码片断创建了一个函数对象,它在配置好的 tape_recorder 实例上调用成员函数 play 。

boost::bind(&tape_recorder::play,&tr)

通常,我们不能保存 bind 所返回的函数对象,但由于 Boost.Function 兼容于任何函数对象,所以它可以。

boost::function<void()> f(boost::bind(&tape_recorder::play,&tr));

注意,这个类也支持调用 record, 它带有一个类型为 const std::string& 的参数,这是由于成员函数 set_function. 因为这个函数对象必须是无参的,所以我们需要绑定上下文以便 record 仍旧能够获得它的参数。当然,这是绑定器的工作。因而,在调用 record 之前,我们创建一个包含被录音的字符串的函数对象。

std::string s="What a beautiful morning...";
record.set_function(boost::bind(&tape_recorder::record,&tr,s));

执行这个保存在 record 的函数对象,将在 tape_recorder 实例 tr 上执行 tape_recorder::record,并传入字符串。有了 Boost.Function 和 Boost.Bind, 就可以实现解耦,让调用代码对于被调用代码一无所知。以这种方式结合使用这两个库非常有用。你已经在这个 command 类中看到了,现在我们该清理一下了。由于 Boost.Function 的杰出功能,你所需的只是以下代码:

typedef boost::function<void()> command;

  **一个简单的回调函数实现

1 void func (void (*p)(void *),void * arg);

与 Boost.Function 一起使用 Boost.Lambda

与 Boost.Function 兼容于由 Boost.Bind 创建的函数对象一样,它也支持由 Boost.Lambda 创建的函数对象。你用 Lambda 库创建的任何函数对象都兼容于相应的 boost::function. 我们在前一节已经讨论了基于绑定的一些内容,使用 Boost.Lambda 的主要不同之处是它能做得更多。我们可以轻易地创建一些小的、无名的函数,并把它们保存在 boost::function 实例中以用于后续的调用。我们已经在前一章中讨论了 lambda 表达式,在那一章的所有例子中所创建的函数对象都可以保存在一个 function 实例中。function 与创建函数对象的库的结合使用会非常强大。

*  下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。

   例子:

代价的考虑

有一句谚语说,世界上没有免费的午餐,对于 Boost.Function 来说也是如此。与使用函数指针相比,使用 Boost.Function 也有一些缺点,特别是对象大小的增加。显然,一个函数指针只占用一个函数指针的空间大小(这当然了!),而一个 boost::function实例占的空间有三倍大。如果需要大量的回调函数,这可能会成为一个问题。函数指针在调用时的效率也稍高一些,因为函数指针是被直接调用的,而 Boost.Function 可能需要使用两次函数指针的调用。最后,可能在某些需要与C库保持后向兼容的情形下,只能使用函数指针。

虽然 Boost.Function 可能存在这些缺点,但是通常它们都不是什么实际问题。额外增加的大小非常小,而且(可能存在的)额外的函数指针调用所带来的代价与真正执行目标函数所花费的时间相比通常都是非常小的。要求使用函数而不能使用 Boost.Function 的情形非常罕见。使用这个库所带来的巨大优点及灵活性显然超出这些代价。

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc);

void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,CompareFunction cmpFunc);
 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 void my1(void * arg)
 5 {
 6     char * str = (char *)arg;
 7     printf("%sn",str);
 8 }
 9 
10 void my2(void * arg)
11 {
12     char * str = (char *)arg;
13     printf("%dn",strlen(str));
14 }
15 
16 void func(void (*f)(void *), void *p)
17 {
18     f(p);
19 }
20 
21 int main(int argc, char const *argv[])
22 {
23     
24     char * msg = "hello";
25     func(my1,(void *)msg);
26     func(my2,(void *)msg);
27 
28     return 0;
29 }

幕后的细节

至少了解一下这个库如何工作的基础知识是非常值得的。我们来看一下保存并调用一个函数指针、一个成员函数指针和一个函数对象这三种情形。这三种情形是不同的。要真正看到 Boost.Function 如何工作,只有看源代码——不过我们的做法有些不同,我们试着搞清楚这些不同的版本究竟在处理方法上有些什么不同。我们也有一个不同要求的类,即当调用一个成员函数时,必须传递一个实例的指针给 function1 (这是我们的类的名字)的构造函数。function1 支持只有一个参数的函数。与 Boost.Function 相比一个较为宽松的投条件是,即使是对于成员函数,也只需要提供返回类型和参数类型。这个要求的直接结果就是,构造函数必须被传入一个类的实例用于成员函数的调用(类型可以自动推断)。

我们将要采用的方法是,创建一个泛型基类,它声明了一个虚拟的调用操作符函数;然后,从这个基类派生三个类,分别支持三种不同形式的函数调用。这些类负责所有的工作,而另一个类,function1, 依据其构造函数的参数来决定实例化哪一个具体类。以下是调用器的基类,invoker_base.

template <typename R, typename Arg> class invoker_base
 {
public:
  virtual R operator()(Arg arg)=0;
};

接着,我们开始定义 function_ptr_invoker, 它是一个具体调用器,公有派生自 invoker_base. 它的目的是调用普通函数。这个类也接受两个类型,即返回类型和参数类型,它们被用于构造函数,构造函数接受一个函数指针作为参数。

template <typename R, typename Arg> class function_ptr_invoker 
  : public invoker_base<R,Arg> {
  R (*func_)(Arg);
public:
  function_ptr_invoker(R (*func)(Arg)):func_(func) {}

  R operator()(Arg arg) {
    return (func_)(arg);
  }
};

这个类模板可用于调用任意一个接受一个参数的普通函数。调用操作符简单地以给定的参数调用保存在 func_ 中的函数。请注意(的确有些奇怪)声明一个保存函数指针的变量的那行代码。

R (*func_)(Arg);

你也可以用一个 typedef 来让它好读一些。

typedef R (*FunctionT)(Arg);
FunctionT func_;

接着,我们需要一个可以处理成员函数调用的类模板。记住,它要求在构造时给出一个类实例的指针,这一点与 Boost.Function 的做法不一样。这样可以节省我们的打字,因为是编译器而不是程序员来推导这个类。

template <typename R, typename Arg, typename T> 
class member_ptr_invoker : 
  public invoker_base<R,Arg> {
  R (T::*func_)(Arg);
  T* t_;
public:
  member_ptr_invoker(R (T::*func)(Arg),T* t)
    :func_(func),t_(t) {}

  R operator()(Arg arg) {
    return (t_->*func_)(arg);
  }
};

这个类模板与普通函数指针的那个版本很相似。它与前一个版本的不同在于,构造函数保存了一个成员函数指针与一个对象指针,而调用操作符则在该对象(t_)上调用该成员函数(func_)。

最后,我们需要一个兼容函数对象的版本。这是所有实现中最容易的一个,至少在我们的方法中是这样。通过使用单个模板参数,我们只表明类型 T 必须是一个真正的函数对象,因为我们想要调用它。说得够多了。

template <typename R, typename Arg, typename T> 
class function_object_invoker : 
  public invoker_base<R,Arg> {
  T t_;
public:
  function_object_invoker(T t):t_(t) {}

  R operator()(Arg arg) {
    return t_(arg);
  }
};

现在我们已经有了这些适用的积木,剩下来的就是把它们放在一起组成我们的自己的 boost::function, 即 function1 类。我们想要一种办法来发现要实例化哪一个调用器。然后我们可以把它存入一个 invoker_base 指针。这里的窃门就是,提供一些构造函数,它们有能力去检查对于给出的参数,哪种调用器是正确的。这仅仅是重载而已,用了一点点手法,包括泛化两个构造函数。

template <typename R, typename Arg> class function1 {
  invoker_base<R,Arg>* invoker_;
public:
  function1(R (*func)(Arg)) : 
  invoker_(new function_ptr_invoker<R,Arg>(func)) {}

  template <typename T> function1(R (T::*func)(Arg),T* p) : 
    invoker_(new member_ptr_invoker<R,Arg,T>(func,p)) {}

  template <typename T> function1(T t) : 
    invoker_(new function_object_invoker<R,Arg,T>(t)) {}

  R operator()(Arg arg) {
    return (*invoker_)(arg);
  }

  ~function1() {
    delete invoker_;
  }
};

如你所见,这里面最难的部分是正确地定义出推导系统以支持函数指针、类成员函数以及函数对象。无论使用何种设计来实现这类功能的库,这都是必须的。最后,给出一些例子来测试我们这个方案。

bool some_function(const std::string& s) {
  std::cout << s << " This is really neat/n";
  return true;
}

class some_class {
public:
  bool some_function(const std::string& s) {
    std::cout << s << " This is also quite nice/n";
    return true;
  }
};

class some_function_object {
public:
  bool operator()(const std::string& s) {
    std::cout << s << 
      " This should work, too, in a flexible solution/n";
    return true;
  }
};

我们的 function1 类可以接受以下所有函数。

int main() {
  function1<bool,const std::string&> f1(&some_function);
  f1(std::string("Hello"));

  some_class s;
  function1<bool,const std::string&> 
    f2(&some_class::some_function,&s);

  f2(std::string("Hello"));

  function1<bool,const std::string&>
    f3(boost::bind(&some_class::some_function,&s,_1));

  f3(std::string("Hello"));

  some_function_object fso;
  function1<bool,const std::string&> 
    f4(fso);
  f4(std::string("Hello"));
}

它也可以使用象 Boost.Bind 和 Boost.Lambda 这样的 binder 库所返回的函数对象。我们的类与 Boost.Function 中的类相比要简单多了,但是也已经足以看出创建和使用这样一个库的问题以及相关解决方法。知道一点关于一个库是如何实现的事情,对于有效使用这个库是非常有用的。

 

 

**  这两个函数接受以下参数:

  运行结果: 

  ·byte * array:指向元素数组的指针(任意类型)。

1 hello
2 5

  ·int size:数组中元素的个数。

 

  ·int elem_size:数组中一个元素的大小,以字节为单位。

  我们并没有直接调用my1和my2这两个函数,而是通过func这个中介来调用他们。

  ·CompareFunction cmpFunc:带有上述原型的指向回调函数的指针。

  当然,回调函数也是可以带回返回值的。

  这两个函数的会对数组进行某种排序,但每次都需决定两个元素哪个排在前面,而函数中有一个回调函数,其地址是作为一个参数传递进来的。对编写者来说,不必介意函数在何处实现,或它怎样被实现的,所需在意的只是两个用于比较的元素的地址,并返回以下的某个值(库的编写者和使用者都必须遵守这个约定):

1 void * func (void * (*p)(void *),void * arg);

  ·-1:如果第一个元素较小,那它在已排序好的数组中,应该排在第二个元素前面。

  例子:

  ·0:如果两个元素相等,那么它们的相对位置并不重要,在已排序好的数组中,谁在前面都无所谓。 

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 void * my1(void * arg)
 5 {
 6     char * str = (char *)arg;
 7     printf("%sn",str);
 8     return (void *)"my1";
 9 }
10 
11 void * my2(void * arg)
12 {
13     char * str = (char *)arg;
14     printf("%dn",strlen(str));
15     return (void *)"my2";
16 }
17 
18 void * func(void *(*f)(void *), void *p)
19 {
20     return f(p);
21 }
22 
23 int main(int argc, char const *argv[])
24 {
25     
26     char * msg = "hello";
27     printf("%sn",func(my1,(void *)msg));
28     printf("%sn",func(my2,(void *)msg));
29 
30     return 0;
31 }

  ·1:如果第一个元素较大,那在已排序好的数组中,它应该排第二个元素后面。

  运行结果:

  基于以上约定,函数Bubblesort()的实现如下,Quicksort()就稍微复杂一点:

1 hello
2 my1
3 5
4 my2

**

   这两种模型都有个共同的特点,含有两类参数,一类是函数指针,另一类是传给回调函数的参数。其中函数指针是必须知道的,只有知道你想要回调的函数才能够调用它。如果回调函数不需要参数则可以传入一个NULL。

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc)
{
 for(int i=0; i < size; i )
 {
  for(int j=0; j < size-1; j )
  {
   //回调比较函数
   if(1 == (*cmpFunc)(array j*elem_size,array (j 1)*elem_size))
   {
    //两个相比较的元素相交换
    byte* temp = new byte[elem_size];
    memcpy(temp, array j*elem_size, elem_size);
    memcpy(array j*elem_size,array (j 1)*elem_size,elem_size);
    memcpy(array (j 1)*elem_size, temp, elem_size);
    delete [] temp;
   }
  }
 }
}

 

**  注意:因为实现中使用了memcpy(),所以函数在使用的数据类型方面,会有所局限。

  什么时候使用回调函数

  对使用者来说,必须有一个回调函数,其地址要传递给Bubblesort()函数。下面有二个简单的示例,一个比较两个整数,而另一个比较两个字符串:

  不确定该任务何时能触发时

**

 

int __stdcall CompareInts(const byte* velem1, const byte* velem2)
{
 int elem1 = *(int*)velem1;
 int elem2 = *(int*)velem2;

 if(elem1 < elem2)
  return -1;
 if(elem1 > elem2)
  return 1;

 return 0;
}

int __stdcall CompareStrings(const byte* velem1, const byte* velem2)
{
 const char* elem1 = (char*)velem1;
 const char* elem2 = (char*)velem2;
 return strcmp(elem1, elem2);
}

 

**  下面另有一个程序,用于测试以上所有的代码,它传递了一个有5个元素的数组给Bubblesort()和Quicksort(),同时还传递了一个指向回调函数的指针。

 

**

 (侵删)

int main(int argc, char* argv[])
{
 int i;
 int array[] = {5432, 4321, 3210, 2109, 1098};

 cout << "Before sorting ints with Bubblesortn";
 for(i=0; i < 5; i )
  cout << array[i] << 'n';

 Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts);

 cout << "After the sortingn";
 for(i=0; i < 5; i )
  cout << array[i] << 'n';

 const char str[5][10] = {"estella","danielle","crissy","bo","angie"};

 cout << "Before sorting strings with Quicksortn";
 for(i=0; i < 5; i )
  cout << str[i] << 'n';

 Quicksort((byte*)str, 5, 10, &CompareStrings);

 cout << "After the sortingn";
 for(i=0; i < 5; i )
  cout << str[i] << 'n';

 return 0;
}

欢迎大家一起讨论

  如果想进行降序排序(大元素在先),就只需修改回调函数的代码,或使用另一个回调函数,这样编程起来灵活性就比较大了。

参考  

**调用约定

 

**  上面的代码中,可在函数原型中找到__stdcall,因为它以双下划线打头,所以它是一个特定于编译器的扩展,说到底也就是微软的实现。任何支持开发基于Win32的程序都必须支持这个扩展或其等价物。以__stdcall标识的函数使用了标准调用约定,为什么叫标准约定呢,因为所有的Win32 API(除了个别接受可变参数的除外)都使用它。标准调用约定的函数在它们返回到调用者之前,都会从堆栈中移除掉参数,这也是Pascal的标准约定。但在C/C 中,调用约定是调用者负责清理堆栈,而不是被调用函数;为强制函数使用C/C 调用约定,可使用__cdecl。另外,可变参数函数也使用C/C 调用约定。

  

  Windows操作系统采用了标准调用约定(Pascal约定),因为其可减小代码的体积。这点对早期的Windows来说非常重要,因为那时它运行在只有640KB内存的电脑上。

  如果你不喜欢__stdcall,还可以使用CALLBACK宏,它定义在windef.h中:

#define CALLBACK __stdcallor

#define CALLBACK PASCAL //而PASCAL在此被#defined成__stdcall

  作为回调函数的C 方法

  因为平时很可能会使用到C 编写代码,也许会想到把回调函数写成类中的一个方法,但先来看看以下的代码:

class CCallbackTester
{
 public:
 int CALLBACK CompareInts(const byte* velem1, const byte* velem2);
};

Bubblesort((byte*)array, 5, sizeof(array[0]),
&CCallbackTester::CompareInts);

  如果使用微软的编译器,将会得到下面这个编译错误:

error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned char *)' There is no context in which this conversion is possible

  这是因为非静态成员函数有一个额外的参数:this指针,这将迫使你在成员函数前面加上static。当然,还有几种方法可以解决这个问题,但限于篇幅,就不再论述了 .

2 补充BBS评论

回调到底层次的看法就是: 

让函数去"自主"调用函数,而不是由你决定. 

typedef void (*VP)(void); 

void Task1() 

         ... 

void Task2() 

         ... 

void EX_CallBack() 

         VP M = NULL; 

         if (condition) 
         { 
             M = Task1; 
         } 
         else 
         { 
             M = Task2; 
         } 

         M(); 

短歌说:它算是一种动态绑定的技术, 
主要用于对某一事件的正确响应. 

3.声明函数指针并回调

程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。

声明函数指针

        回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:

void f();// 函数原型

上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:

void (*) ();

        让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

// 获得函数指针的大小
unsigned psize = sizeof (void (*) ()); 

// 为函数指针声明类型定义
typedef void (*pfv) ();

pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。

指针变量应该有一个变量名:

void (*p) (); //p是指向某函数的指针

        p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:

void func() 
{
/* do something */

p = func; 

p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。

传递回调函数的地址给调用者

        现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:

void caller(void(*ptr)())
{
ptr(); /* 调用ptr指向的函数 */ 
}
void func();
int main()
{
p = func; 
caller(p); /* 传递函数地址到调用者 */
}

        如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。

调用规范

        到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C 的编译器规范。许多编译器有几种调用规范。如在Visual C 中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

        将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:

// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int); 

// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int)); 

// 在p中企图存储被调用函数地址的非法操作
__cdecl int(*p)(int) = callee; // 出错

        指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列

4。函数指针和回调函数

函数指针和回调函数

你不会每天都使用函数指针,但是,它们确有用武之地,两个最常见的用途是把函数指针作为参数传递给另一个函数以及用于转换表(jump table)。

       【警告】简单声明一个函数指针并不意味着它马上就可以使用。和其它指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。下面的代码段说明了一种初始化函数指针的方法。
        int    f(int);
        int    (*pf)(int)=&f;

        第 2 个声明创建了函数指针 pf ,并把它初始化为指向函数 f 。函数指针的初始化也可以通过一条赋值语句来完成。 在函数指针的初始化之前具有 f 的原型是很重要的,否则编译器就无法检查 f 的类型是否与 pf 所指向的类型一致。

        初始化表达式中的 & 操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。 & 操作符只是显式地说明了编译器隐式执行的任务。

        在函数指针被声明并且初始化之后,我们就可以使用三种方式调用函数:
        int    ans;
    
        ans=f(25);
        ans=(*pf)(25);
        ans=pf(25);

        第 1 条语句简单地使用名字调用函数 f ,但它的执行过程可能和你想象的不太一样。 函数名 f 首先被转换为一个函数指针,该指针指定函数在内存中的位置。然后, 函数调用操作符调用该函数,执行开始于这个地址的代码。
        第 2 条语句对 pf 执行间接访问操作,它把函数指针转换为一个函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第1条是完全一样的。
        第 3 条语句和前两条的效果是一样的。间接访问并非必需,因为编译器需要的是一个函数指针。

        (一)回调函数
        这里有一个简单的函数,它用于在单链表中查找一个值。它的参数是一个指向链表第 1 个节点的指针以及那个需要查找的值。

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

        这个函数看上去相当简单,但它只适用于值为整数的链表。如果你需要在一个字符串链表中查找,你不得不另外编写一个函数。这个函数和上面那个函数的绝大部分代码相同,只是第 2 个参数的类型以及节点值的比较方法不同。

        一种更为通用的方法是使查找函数与类型无关,这样它就能用于任何类型的值的链表。我们必须对函数的两个方面进行修改,使它与类型无关。

        首先,我们必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较。这个目标听上去好像不可能,如果你编写语句用于比较整型值,它怎么还可能用于其它类型如字符串的比较呢? 解决方案就是使用函数指针。调用者编写一个比较函数,用于比较两个值,然后把一个指向此函数的指针作为参数传递给查找函数。而后查找函数来执行比较。使用这种方法,任何类型的值都可以进行比较。

        我们必须修改的第 2 个方面是向比较函数传递一个指向值的指针而不是值本身。比较函数有一个 void    * 形参,用于接收这个参数。然后指向这个值的指针便传递给比较函数。(这个修改使字符串和数组对象也可以被使用。字符串和数组无法作为参数传递给函数,但指向它们的指针却可以。)

        使用这种技巧的函数被称为回调函数(callback    function),因为用户把一个函数指针作为参数传递其它函数,后者将”回调“用户的函数。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。
      
        【提示】
       在使用比较函数的指针之前,它们必须被强制转换为正确的类型。因为强制类型转换能够躲开一般的类型检查,所以你在使用时必须格外小心,确保函数参数类型是正确的。

        在这个例子里,回调函数比较两个值。查找函数向比较函数传递两个指向需要进行比较的值的指针,并检查比较函数的返回值。例如:零表示相等的值,现在查找函数就与类型无关,因为它本身并不执行实际的比较。确实,调用者必须编写必需的比较函数,但这样做是很容易的,因为调用者知道链表中所包含的值的类型。如果使用几个分别包含不同类型值的链表,为每种类型编写一个比较函数就允许单个查找函数作用于所有类型的链表。

        程序段01 是类型无关的查找函数的一种实现方法。 注意函数的第 3 个参数是一个函数指针。这个参数用一个完整的原型进行声明。同时注意虽然函数绝不会修改参数 node 所指向的任何节点,但 node 并未被声明为 const 。如果 node 被声明为 const,函数将不得不返回一个const结果,这将限制调用程序,它便无法修改查找函数所找到的节点。

        /*
        **程序 01 ——类型无关的链表查找函数
        **在一个单链表中查找一个指定值的函数。它的参数是一个指向链表第 1 个节点的指针、一个指向我们需要    查找的值的指针和一个函数指针。
        **它所指向的函数用于比较存储于链表中的类型的值。
        */
        #include    <stdio.h>
        #include    "node.h"
      
        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;
        }

        指向值参数的指针和 &node->value 被传递给比较函数。后者是我们当前所检查的节点值。
      
        在一个特定的链表中进行查找时,用户需要编写一个适当的比较函数,并把指向该函数的指针和指向需要查找的值的指针传递给查找函数下面是一个比较函数,它用于在一个整数链表中进行查找。
        int
        compare_ints( 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 *。

        (二)转移表
        转换表最好用个例子来解释。下面的代码段取自一个程序,它用于实现一个袖珍式计算器。程序的其他部分已经读入两个数(op1和op2)和一个操作数(oper)。下面的代码对操作符进行测试,然后决定调用哪个函数。

        switch( oper ){
        case ADD:
                result = add( op1, op2);
                break;
        case SUB:
                result = sub( op1, op2);
                break;
        case MUL:
                result = mul( op1, op2);
                break;
        case DIV:
                result = div( op1, op2);
                break;
        
          ......

        对于一个新奇的具有上百个操作符的计算器,这条switch语句将非常长。

        为什么要调用函数来执行这些操作呢? 把具体操作和选择操作的代码分开是一种良好的设计方法,更为复杂的操作将肯定以独立的函数来实现,因为它们的长度可能很长。但即使是简单的操作也可能具有副作用,例如保存一个常量值用于以后的操作。

        为了使用 switch 语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。

        创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。

        double add (double,double);
        double sub (double,double);
        double mul (double,double);
        double div (double,double);
        ......
        double ( *oper_func[] )( double, double)={
            add,sub,mul,div,...
        };

        初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子假定ADD是0 ,SUB是1,MUL是2,依次类推。

        第 2 个步骤是用下面这条语句替换前面整条 switch 语句!
        result = oper_func[ oper ]( op1,op2 );
        oper从数组中选择正确的函数指针,而函数调用操作符执行这个函数。

 

转载: 


本文由星彩网app下载发布于计算机编程,转载请注明出处:回调函数,Python回调函数用法实例详解

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。