回调的原理、实现与应用

C++能看不能写,网上找的写的很好的,比Java看着好理解多了,另外我的git里面有大量的相关资源:

https://github.com/singgel/eight-sorting-algorithms/tree/master/src/test/java/com/hks/eightsortingalgorithms

什么是回调

    函数指针有什么用呢?一个最常用的地方就是回调。

    什么回调?维基百科是这样解释的:回调是一段可执行的代码通过参数传递给别一段代码,以期望在一个合适的时间调用这个参数(可执行的代码)。

 

参考: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方法的方式变为多线程方式使用

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值