C++能看不能写,网上找的写的很好的,比Java看着好理解多了,另外我的git里面有大量的相关资源:
什么是回调
函数指针有什么用呢?一个最常用的地方就是回调。
什么回调?维基百科是这样解释的:回调是一段可执行的代码通过参数传递给别一段代码,以期望在一个合适的时间调用这个参数(可执行的代码)。
参考:In computer programming, a callback is a piece of executable code that is passed as an argument to other code, which is expected to call back(execute) the argument at some convenient time.
如果你已明白回调或理解上面这一句话,可以漂过,感觉头昏的请往下看。
从一个需求开始
先不扯淡,直接拿事实说话。假设有这么一个需求:
有一Person类定义如下:
struct Person
{
int age;
float weight;
float height;
};
现要对Person的一组对象进行排序,但并没有确定根据什么规则来排序,有时需要根据年龄进行排序,有时需要根据身高进行排序,有时可能是根据身高和体重的综合情况来排序,还有可能……
你可能会想到这样写,定义三个函数分别根据年龄、体重、身高进行排序:
void SortByAge(Person* persons, int count);
void SortByWeight(Person* persons, int count);
void SortByHeight(Person* persons, int count);
如果要根据身高和体重的综合情况来排序,那你还得再定义一个函数。这样是不是代码冗余且很繁琐?但如果你会用回调,这个问题就会很简单。
用回调实现对Person的排序:
typedef int (*Compare)(const Person&, const Person&);
//交换两个元素
void swap(Person* p1, Person *p2)
{
Person p = *p1;
*p1 = *p2;
*p2 = p;
}
//排序(本例中采用冒泡排序)
void PersonSort(Person* persons, int count, Compare pCompare)
{
for (int i = 0; i < count-1; i ++)
{
for (int j = 0; j < count - i -1; j++)
{
if (pCompare(persons[j], persons[j+1]) > 0)
{
swap(persons+j, persons+j+1);
}
}
}
}
如果你要根据年龄来进行排序,只要实现一个Compare类型的函数,再调用上面的PersonSort函数就可以实现根据年龄排序的功能。如:
int CompareByAge(const Person& p1, const Person& p2)
{
return p1.age - p2.age;
}
void TestCallBack()
{
//创建Person的一组对象persons,对象中的各个值为0到100的随机数
srand((unsigned)time(NULL));
Person persons[10];
for(int i = 0; i < 10; i ++)
{
persons[i].age = rand()%100;
persons[i].weight = rand()%100;
persons[i].height = rand()%100;
}
//【todo】
//根据年龄进行排序。
PersonSort(persons, 10, CompareByAge);
for(int i = 0; i < 10; i ++)
{
std::cout << persons[i].age << "\t" << persons[i].weight << "\t" << persons[i].height << std::endl;
}
}
说明:以上调用TestCallBack函数时需要包含头文件<iostream>、<time.h>
这样如果需求发生变更(如要根据每个Person身高和体重的总和来排序),只需求再定义一个Compare类型的函数,而不用再对PersonSort函数做任何改动。如下:
int CompareByHeightWeight(const Person& p1, const Person& p2)
{
return (p1.height + p1.weight) - (p2.height + p2.weight);
}
void TestCallBack()
{
//创建Person的一组对象persons,对象中的各个值为0到100的随机数
srand((unsigned)time(NULL));
Person persons[10];
for(int i = 0; i < 10; i ++)
{
persons[i].age = rand()%100;
persons[i].weight = rand()%100;
persons[i].height = rand()%100;
}
//【todo】
//根据年龄进行排序。
PersonSort(persons, 10, CompareByHeightWeight);
for(int i = 0; i < 10; i ++)
{
std::cout << persons[i].age << "\t" << persons[i].weight << "\t" << persons[i].height << "\t" << persons[i].weight + persons[i].height << std::endl;
}
}
C++ STL中的Sort(在<algorithm>头文件中)用的就是这种技术:
template<class RandomAccessIterator>
void sort(
RandomAccessIterator first,
RandomAccessIterator last
);
template<class RandomAccessIterator, class Predicate>
void sort(
RandomAccessIterator first,
RandomAccessIterator last,
Predicate comp
);
Parameters
first
A random-access iterator addressing the position of the first element in the range to be sorted.
last
A random-access iterator addressing the position one past the final element in the range to be sorted.
comp
User-defined predicate function object that defines the comparison criterion to be satisfied by successive elements in the ordering. This binary predicate takes two arguments and returns true if the two arguments are in order and false otherwise. This comparator function must impose a strict weak ordering on pairs of elements from the sequence. For more information, see Algorithms.
通过上面一个实例的分析,应该对回调有所了解和认识了吧!回调函数说白了就是定义一个函数,然后通过参数传递给另一个函数调用。回调不仅是一种技术,更是一种编程思想,上面是通过回调函数来实现的,但它不仅限于回调函数,也可以用其它的技术实现(如面向对象的实现)。
-----------------------------------以下是Java的实现-----------------------------------
所谓的回调,就是程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序。程序员B要让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法。下面是例子。
1. 首先定义一个类Caller,按照上面的定义就是程序员A写的程序a,这个类里面保存一个接口引用。
public class Caller {
private MyCallInterface callInterface;
public Caller() {
}
public void setCallFunc(MyCallInterface callInterface) {
this.callInterface = callInterface;
}
public void call() {
callInterface.printName();
}
}
2. 当然需要接口的定义,为了方便程序员B根据我的定义编写程序实现接口。
public interface MyCallInterface {
public void printName();
}
3. 第三是定义程序员B写的程序b
public class Client implements MyCallInterface {
@Override
public void printName() {
System.out.println("This is the client printName method");
}
}
4. 测试如下
public class Test {
public static void main(String[] args) {
Caller caller = new Caller();
caller.setCallFunc(new Client());
caller.call();
}
}
看到这里应该明白什么是回调了,有些文章介绍的很好,但是刚开始没看明白,是因为把第3步的类省略,直接写成匿名类了。
5. 在测试方法中直接使用匿名类,省去第3步。
public class Test {
public static void main(String[] args) {
Caller caller = new Caller();
// caller.setCallFunc(new Client());
caller.setCallFunc(new MyCallInterface() {
public void printName() {
System.out.println("This is the client printName method");
}
});
caller.call();
}
}
这样就能看懂了吧,在程序B自己的printName方法,在运行A的时候怎么来调用呢,就是通过接口来调用,接口是在A初始化的时候装载了B的printName方法,来达到回调的目的。
为什么要使用回调机制
程序设计一个主要的目标就是“将发生变化的东西同保持不变的东西分隔开”。这样就可以编写通用的代码,以后增加新类型时便容易实现代码的重复利用。
采用“回调”技术。利用回调,经常发生变化的那部分代码会封装到它自己的类内,而总是保持相同的代码则“回调”发生变化的代码。这样一来,不同的对象就可以表达不同的比较方式,同时向它们传递相同的排序代码。
也就是说,Java无法将method方法作为参数传递(以往你的做法是传递一个class类,然后再使用你传过来的这个class类里面的方法,这样你的代码就是C++章节里面说的“ 这样是不是代码冗余且很繁琐?”),而C++可以通过指针来传递。所以才有了上面的那些鬼玩意儿。
------------------------------------------------在WINDOWS中讲解------------------------------------------------
在WINDOWS中,程序员想让系统DLL调用自己编写的一个方法,于是利用DLL当中回调函数(CALLBACK)的接口来编写程序,使它调用,这个就称为回调。在调用接口时,需要严格的按照定义的参数和方法调用,并且需要处理函数的异步,否则会导致程序的崩溃。
要不然你的客户端是怎么在桌面上跑起来的,而且被主系统能够监控和调度着,对不?
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
好了 你以上的内容搞懂了之后呢,咱们来看一下:
函数回调机制、异步函数回调机制
函数回调机制,一种双向调用思想,简单来说就是,如下图所示:
在层次一中的方法一(函数)调用层次二中的方法,并传入函数二的地址,而这个被调用的方法又会调用层次一中的方法,这个最后被调用的方法二就是回调方法。方法三调用方法二就是回调的过程。一个有意思的例子,大家可以先感受一下:“诸葛亮给赵子龙一个锦囊,吩咐他危急时打开按锦囊指示办, 锦囊里的命令就是回调函数,危急时刻就是回调的时机。”
在Java中,这个“层次”可以理解为类,是两个类互相调用对方的方法;也可以理解为应用类(高层)调用类库方法(低层),并传入一个自定义的方法以完成某些功能。
说到“调用”,模块之间总是存在这一定的接口,模块之间通过这些接口调用以通信联系,从调用方式上看,可以分为三类:同步调用、回调和异步调用。
同步调用是一种阻塞式调用,也是我们在写程序中经常使用的;
回调是一种双向的调用模式;
异步调用是一种类似消息或事件的机制,解决了同步阻塞的问题,举例来讲:A通知B后,他们各走各的路,互不影响,不用像同步调用那样,A通知B后,非得等到B走完后,A才继续走。回调是异步调用的基础。下面以一个网络上很流行的例子为基础,理解异步回调机制。
异步回调典型例子:
提问者A有个问题"1+1=?",于是A打电话给回答者B,B说他现在很忙,忙完了才能给他想答案,A心想我不能这么一直等着把,于是说:“那咱们约定好,B你想出答案了以打电话的形式告诉我”,挂了电话A也去忙他自己的事了,过了一会B想出答案按A约定好的方式打电话告诉了B答案。
下面以代码形式描述这个过程
/**
* 这是一个回调接口,里面定义的方法就是回调函数
*/
public interface CallBack {
/**
* 这是一个回调函数,用于回答者B知道答案后给提问者A回电话,并告知提问者A答案是什么
* 这个回电话的方式callBack是提问者A确定的,所以这个方法的实现类是A类
* 这个回电话的内容result是回答者B提供的,所以这个变量的值是在B类中确定的
*/
public void callBack(String result);
}
/**
* 提问者A类
*/
public class A implements CallBack{
/**
* 提问者A是知道回答者B的联系方式的
* 这里以一个B类引用代表,并在构造方法中传入B实例对象
*/
private B b;
public A(B b){
this.b = b;
}
/**
* 提问者A向回答者B打电话提问题
* 这里以一个call方法表示,并把问题参数传入
*/
public void call(final String question){
/**
* 建立提问者A线程,与回答者B线程结合,构成一个异步的环境
*/
new Thread(new Runnable() {
@Override
public void run() {
/**
* B接电话,听问题
* 这里以调用回答者B的answer方法表示,传入回调方法类参数、问题参数,以表示谁打的电话,问啥了
* 因为A类实现了CallBack接口,所以A类就是回调方法类,回调方法类实现了回调方法
*/
b.answer(A.this, question);
}
}).start();
/**
* 提问者提完问,去干别事情
*/
doOtherThing();
}
public void doOtherThing(){
System.out.println("我是提问者A,我问完问题就去干别的事情了!");
}
/**
* 刚刚说到,这个回电话的方式callBack是提问者A确定的,所以这个方法的实现类是A类
* 所以这里实现回调方法,代表回复的方法是回电话,由回答者B调用
*/
@Override
public void callBack(String result) {
System.out.println("B调用A定义的回调函数:回答者B告诉提问者A,问题的答案是:"+ result);
}
}
/**
* 回答者B类
*/
public class B {
/**
* 回答者B接电话,听问题 这里以调用回答者B的answer方法表示,传入回调方法类、问题参数,以表示谁打的电话,问啥了
*/
public void answer(CallBack callBack, String question) {
System.out.println("A调用B的接电话方法:我是回答者B,提问者A问的问题是:" + question);
/**
* 模拟回答者B先忙自己的事
*/
System.out.println("我是回答者B,我接完电话先去忙我自己的事!");
for (int i = 0; i < 100000; i++) {
}
String result = "2";
System.out.println("我是回答者B,我知道了答案是:" + result);
/**
* 调用回调函数,打电话告知A答案是什么
*/
callBack.callBack(result);
}
}
/**
* 场景测试类
*/
public class test {
public static void main(String args[]){
/**
* 实例化回答者B
*/
B b = new B();
/**
* 实例化提问者A
*/
A a = new A(b);
/**
* A向B提问,开始
*/
a.call("1 + 1 = ?");
}
}
(例子原版源自:xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/17483273))
执行结果:
综上,你基本上可以看出来,回调呢就是method方法当做参数进行传递;而异步回调呢,不仅仅是method方法当做参数传递,而且还把你传递过去之后,它使用这个method方法的方式变为多线程方式使用