提出问题

回调函数这个概念一直以来都对它处于迷迷糊糊的理解中,总感觉不能很好的解释这玩意儿,最近在学习OpenCV的时候又正好碰见了它,所以,心想这次我就把你给搞定了吧。

先将OpenCV中的相关代码附上

#include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <imgproc.hpp> using namespace std; using namespace cv; ​ //TrackBar发生改变的回调函数 void onChangeTrackBar(int pos, void* userdata); ​ //主函数 int main() { //trackbar的值 int posTrackBar = 0; //trackbar的最大值 int maxValue = 255; ​ //读入图像,以灰度图形式读入 Mat img = imread("D:/code/repos/2.jpg", 0); ​ //新建窗口 namedWindow("二值化"); imshow("二值化", img); ​ //创建trackbar,我们把img作为数据传进回调函数中 createTrackbar("pos", "二值化", &posTrackBar, maxValue, onChangeTrackBar, &img); waitKey(); return 0; } ​ // 回调函数 void onChangeTrackBar(int pos, void* usrdata) { // 强制类型转换 Mat src = *(Mat*)(usrdata); Mat dst; ​ // 二值化 threshold(src, dst, pos, 255, 0); imshow("二值化", dst); }

关于这个程序的相关效果,大家可以去看这篇文章。https://mp.toutiao.com/profile_v4/graphic/preview?pgc_id=6880767810209841677

查找资料

没有条件,创造条件也要上,不理解不要紧,赶紧去各大网站上搜索了一下关于回调函数的文章,受到了很多的启发,结合自己的理解,我对回调函数又有了一些新的想法,下面就来说说我对回调函数的看法。

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

上面这段解释出自于百度百科,看完之后相信你会有这种感觉,这说的是啥玩意?这。。。能好好说人话吗?其实很多这种概念你去翻阅比较官方的回答,都会有这种解释了跟没解释的感觉,完全无法理解嘛。

没事的,我也是这样的,大多数人估计都这样。

于是乎我去找了一下其它的回答:

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件

作者:常溪玲 链接:https://www.zhihu.com/question/19801131/answer/13005983

嗯?好像有点感觉了,我似乎听懂了那么一些,那么如果对应实际的程序代码有事怎么一回事呢?

现在我们来整理一下思路:

  1. 需要一个回调函数
  2. 需要一个调用回调函数的函数

然后我们先创建一个回调函数,从前面的相关说明我们知道回调函数是一个通过函数指针调用的函数。简单来说它就是一个函数而已,其它的可以先不用管。

void on_callbackBJ() { printf("I from beijing.\n"); } void on_callbackSH() { printf("I from shanghai.\n"); }

现在需要一个调用回调函数的函数,好的这也是个函数,那么它有什么不同呢?我们可以将其叫做中介函数,它就像一个中间商一样调用了另外一个函数,并不是在函数体内调用,而是通过形参调用,并且需要调用的是另一个函数的地址,这样被调用那个函数就成为回调函数。

void wherecallback(void *name()) { printf("where are you from?\n"); name(); }

好的,我现在了解了这些,那么这个函数到底有什么用处呢?为了弄明白这种函数的奥妙,首先提出三个问题:

  1. 回调函数是什么东西?
  2. 回调函数怎么开发,怎么使用?
  3. 回调函数的作用,应该在什么情况下使用?

打开百度搜索了好多资料,如下:

第一个问题:


其实回调就是一种利用函数指针进行函数调用的过程.

为什么要用回调呢?比如我要写一个子模块给你用, 来接收远程socket发来的命令.当我接收到命令后, 需要调用你的主模块的函数, 来进行相应的处理.但是我不知道你要用哪个函数来处理这个命令, 我也不知道你的主模块是什么.cpp或者.h, 或者说, 我根本不用关心你在主模块里怎么处理它, 也不应该关心用什么函数处理它...... 怎么办?

使用回调!

回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可以在回调函数里完成你要做的事。

看了这么多的资料,我只将每位的定义总结一下就一句话:回调函数就是函数指针的一种用法。

第二个问题:

我实现了一个很简单的回调函数。

#include <stdio.h> ​ void on_callbackBJ() { printf("I from beijing.\n"); } void on_callbackSH() { printf("I from shanghai.\n"); } void wherecallback(void* name()) { printf("where are you from?\n"); name(); } int main() { wherecallback(on_callbackBJ); wherecallback(on_callbackSH); return 0; }

第三个问题:

用过STL的人都知道,在STL中众多算法和程序都用到回调函数,这实现了一种策略。只要任何符合我的标准的函数和计算都可以用我这个公式。你可以实现各种各样的回调函数,只要符合我的格式就能用。

就上面的程序来说,你只要函数格式符合cllback第二个参数的格式不论你给别人做饭、铺床叠被都可以正常工作。这就是回调的作用,把回调实现留给别人。

小结

综合上面的回答,我理解的回调函数及其作用就是:我自己写了一个实现特定功能的函数B,这个就是回调函数。当我在调用某个API函数时,这个函数的某个参数需要一个指向函数的指针,也就是需要一个回调函数,我并不需要关心这个API函数的内部是什么,只需要知道它所实现的功能以及各个参数的含义,然后将我所编写的实现特定功能的函数B作为参数传入进去,当然我也可以传入函数C或者函数D之类满足我需要的函数。

回归正题

OK,现在返回到正题中去,如何理解OpenCV中的回调函数,如前文的分析,我现在需要调用一个OpenCV的一个API函数creatTrackbar(),他的函数原型:

int createTrackbar(conststring& trackbarname, conststring& winname, int* value, int count, TrackbarCallback onChange=0,void* userdata=0);

我不需要知道这函数内部是什么样的,我需要它各个参数的含义:

第一个参数,const string&类型的trackbarname,表示轨迹条的名字,用来代表我们创建的轨迹条。

第二个参数,const string&类型的winname,填窗口的名字,表示这个轨迹条会依附到哪个窗口上,即对应namedWindow()创建窗口时填的某一个窗口名。

第三个参数,int* 类型的value,一个指向整型的指针,表示滑块的位置。并且在创建时,滑块的初始位置就是该变量当前的值。

第四个参数,int类型的count,表示滑块可以达到的最大位置的值。PS:滑块最小的位置的值始终为0。

第五个参数,TrackbarCallback类型的onChange,首先注意他有默认值0。这是一个指向回调函数的指针,每次滑块位置改变时,这个函数都会进行回调。并且这个函数的原型必须为void XXXX(int,void*);其中第一个参数是轨迹条的位置,第二个参数是用户数据(看下面的第六个参数)。如果回调是NULL指针,表示没有回调函数的调用,仅第三个参数value有变化。

第六个参数,void*类型的userdata,他也有默认值0。这个参数是用户传给回调函数的数据,用来处理轨迹条事件。如果使用的第三个参数value实参是全局变量的话,完全可以不去管这个userdata参数。

以及它的作用:

这个createTrackbar函数,为我们创建一个具有特定名称和范围的轨迹条(Trackbar,或者说是滑块范围控制工具),指定一个和轨迹条位置同步的变量。而且要指定回调函数onChange(第五个参数),在轨迹条位置改变的时候来调用这个回调函数。并且我们知道,创建的轨迹条显示在指定的winname(第二个参数)所代表的窗口上。

剩下的就是回调函数的处理,想要什么的功能就写什么样的函数传进去,如果我要实现二值化的处理,我会这样

void onChangeTrackBar(int pos, void* usrdata) { // 强制类型转换 Mat src = *(Mat*)(usrdata); Mat dst; ​ // 二值化 threshold(src, dst, pos, 255, 0); imshow("二值化", dst); }

结果:

c语言书籍中无回调函数(cc笔记)(1)

c语言书籍中无回调函数(cc笔记)(2)

如果我要实现两张图片的混合,并且可以调节其透明度我会这样:

void on_Trackbar(int , void* ) { alphaVal = (double)alphaValSlider / maxAlphaVal; betaVal = (1.0 - alphaVal); ​ addWeighted(src1, alphaVal, src2, betaVal, 0.0, dst); imshow("滑动窗口",dst); }

结果:

c语言书籍中无回调函数(cc笔记)(3)

c语言书籍中无回调函数(cc笔记)(4)

,