mark一下学习笔记,加深印象!
1.软件系统的分层:
在我们遇到的实际算法工程项目中,我们希望整个软件系统的分层如下图所示:
应用层:位于最上层,也就是提供给用户的功能接口,例如人脸活体检测接口。可以供用户的main函数直接调用,依赖于中间层
中间层:位于中间层,为应用层的功能函数提供动态库函数接口,例如综合多种人脸活体检测算法的接口,依赖于底层算法层
底层:位于最底层,为中间层提供需要的单种算法sdk
分层后,我们希望函数调用关系都是从上到下,以确保单向依赖!
为啥要保证单向依赖?这样做的好处,可以保证某个层的依赖者可以轻易更换。举个例子:中间层或者应用层可以在底层算法sdk不变的情况下,轻易更改该层的功能函数。
但是,实际工程中的单向依赖是比较难的,相邻层之间会不可避免地出现相互依赖的关系。这就意味着,如果你想更改某一层的函数接口,就得更改其依赖的层函数,这样对于工程代码的编写和维护会增加很多工作量!
因此,为了解决这个问题,就引出了回调函数,其最大的好处:是一个可以将双向依赖改为单向依赖的好工具!
2.函数指针:
学习回调函数之前,需要先掌握函数指针的用法,其定义形式是:
int main()
{
//函数指针定义如下,写几个例子:
void(*p)(void);//定义一个 指向无参、无返回值的 函数指针
//p只能指向void fun(void)形式的函数
void(*p1)(int, int);//定义一个 指向有两个整形参数、无返回值的 函数指针
//p1只能指向void fun(int a, int b)形式的函数
int(*p2)(int);//定义一个 指向有一个整形参数、一个整形返回值的 函数指针
//p2只能指向int fun(int a)形式的函数
}
具体使用如下:
//先定义一个函数
void fun(int a)
{
std::cout << "fun()" << std::endl;
}
//main函数
int main()
{
void(*p)(int);
p = fun;//把fun函数赋值给p,让p指向fun的整个函数 函数实际上是个代码块
//函数运行
p();//函数指针运行(简便方法)
(*P)();//原始运行方法,表示先通过*拿到p指向的对象,然后让它运行
}
"fun()"
"fun()"
总结来说,就是使用函数指针指向函数的代码块,然后使用函数指针实现函数运行。
3.回调函数:
定义:回调函数是指 使用者自己定义一个函数,实现这个函数的程序内容,然后把这个函数(入口地址)即函数指针作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。
函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数。
特征就是:在定义的模块中不运行,交给另一个模块运行的函数
举例子:
假设你现在有两个文件,分别为run.c和main.c,对应被依赖层和依赖层。现在你希望在main.c文件中定义的函数可以在run.c中运行,这个函数就是回调函数。
run.c:
//run.c文件
//定义的函数指针,指向定义于main.c中的回调函数
void(*step)(void) = NULL;
void run(void)
{
if (!step)
step();//指向的回调函数运行
}
main.c:
//main.c
//run.c中的函数指针
extern void(*step)(void);
//定义回调函数
void callback_fun(void)
{
std::cout << "callback fun()" << std::endl;
}
int main()
{
//给run.c中的函数指针赋值,指向main.c中的回调函数
step = callbakc_fun;
//运行run.c中的run()函数
run();
return 0;
}
输出:
"callback fun()"
这样的好处:main.c->依赖于run.c,而run.c可以不知道main.c的存在,这就是单向依赖关系
适用场景:调用对象是一个黑盒子(其他公司打包的库),你在编程时,想要改变库里的某些默认动作