回调函数(callback)是什么?

百度百科上的定义:回调函数就是一个被作为参数传递的函数。

回调是英文callback的直译,call是调用,back象征回过来。字面解释是让系统回过来调用我们指定好的函数。我们可以保存一个过程,用于以后在特定时间使用。比如设置一段代码,让操作系统在触发特定事情时调用(如按下键盘,按下鼠标时);又如开发游戏时,设置一段代码,在两个物体接触时使用这段代码来处理(子弹和敌人的接触,勇士和奖励的接触都可以触发);设计模式中策略模式中的传入的策略也有回调函数的影子。事件监听器(EventListener)也是回调的体现。

在C语言中,回调函数就是一个函数指针。C++中,回调函数可以是一个仿函体。Java中回调函数可以是一个接口类型的引用。

回调函数和多态是经典的两种用来回调的机制。

使用多态时我们会创建一个子类来继承某个父类,重写需要的函数。我们一般用这个子类创建的对象代替之前的对象,当系统以父类的指针或引用调用函数时,就会调用我们重写后的函数,进而达到回调的效果。

基本所有经典框架如QT,cocos中都支持提供该方法。如QT中可以继承QWindow来实现自定义的功能,cocos可以通过继承Scene重写init函数来提供自定义的控制。

多态本质上也是使用回调函数,把回调函数存储在类对应的虚表上。

想要理解回调函数,可以先理解对应的实现载体,如C/C++中常用的函数指针。下面来讲下函数指针

C/C++中一个非常重要的概念是内存的概念。内存我们可以理解为一个由亿万个字节组成的数组,每个字节能存储8个二进制数字,所以每个字节的数据范围都是[0,256)。所有字节紧紧挨在一起,根据顺序给每一个字节一个序号。最开始的字节的序号是0,下一个字节的需要是1,以此类推,所有字节都有自己的序号,根据序号也只会对应某一个字节。这个序号我们常称之为内存地址。敲黑板,内存地址就是某一个字节的序号。

我们常说的一个变量,就是几个相邻的字节。根据变量类型的不同,字节的数量也会不同,但这些字节一定是相邻的(为求理解,本文不涉及虚拟内存相关知识)。比如C++中一个int变量就是相邻的4个字节(本文默认程序是32位程序,大多数学生刚接触都是这样),C++中一个float变量也是相邻的4个字节。一个double变量是相邻的8个字节,char变量是1个字节。一个变量说白了就是对应到内存中的几个相邻的字节,操作系统和编译器会保证我们在使用不同的变量时对应的字节不会重复。修改变量a的值,只有a对应的字节会改变,另一个变量b绝对不会随着修改a而改变。这是能安全开发程序的基础。如果我们修改a变量,b变量或其他变量也可能会改变,那就代表着计算机程序不能合理的去做任何事,因为所有游戏中的数值都是变量存储,银行程序中顾客存储的金额也是用变量来存储,ICU中的程序关键信息也是用变量来存储,不同变量会对应到不同字节大家一定要有概念。

变量的内存地址,就是变量对应的几个字节的首字节地址(几个相邻字节中序号最小的字节的序号)。

变量概念理解了,下边就可以好好理解下指针:

指针也是一种变量,叫做指针变量(=w=)。不过和普通的变量不同,普通变量是用几个字节来存储数据,而指针变量本身用来存储其他变量的内存地址。用我们新学的大白话来说,指针变量就是存储某个字节的序号的。32位程序下,一个指针变量只需要4个字节就有能力存储内存中任意一个字节的序号。根据指针变量中存储的内容,我们一定能找到对应字节。再根据指针变量的类型,我们能知道对应变量的类型。如一个int*类型的指针,表示内部存储的字节序号开始4个字节可以作为一个int来使用(int在32位程序中是4个字节)。如果是一个double*类型的指针,表示这个指针对应的字节开始8个字节可以作为一个double使用。进阶一下,int**也是指针变量,只不过用该指针变量能找到一个int*。找的过程就是*(学名解引用)。

int a=5;//创建一个4字节的变量,用标识符a来对应这个变量。
int* b=&a;//创建一个int*类型的变量,用标识符b来对应这个变量。该变量占4个字节。给该变量赋值为a的地址。

*b;//通过指针b来找到a变量对应的字节。
*b=7;//此种方法修改的字节,其实也是a对应的字节。所以a的值也会改变

在看任何代码中,脑海里都要有上述内存的概念,这是所有c/c++进阶的人脑中一定会有的概念。

我们现在使用的计算机都算是冯诺依曼结构(冯·诺依曼结构_百度百科),大白话说就是,数据和代码都在内存中存储在字节里。我们可以把数据当代码看,也可以把代码当数据看。这也是黑客的攻击电脑主要手段,只要让系统把黑客精心设计的数据当作代码后,系统一去执行,那系统就被黑客接管了。

话外的不多聊,上一段的主要概念是,代码也是存储在内存中,所以代码也有自己的地址。而在c/c++中我们写代码都是在函数(function)中定义。所以表示一段代码就用这个函数对应的内存的首地址来表示,也就称为函数指针

根据函数指针,我们可以找到一段代码。所以我们可以保存或传入我们自己定义的函数的函数指针,来告诉系统在某些时候调用下这块代码来控制系统的一些行为。按下按钮后有什么行为,进入某个场景后有什么行为,回调函数就是又方便又快捷的控制手段。


本文关联知识点:内存概念;变量;内存地址;指针;函数指针;

引申知识点:设计模式-策略模式;事件监听器(EventListener);冯诺依曼结构

发布于 2021-05-02 23:21