[C.C++] 关于C++ 回调函数(callback) 精简且实用

226 0
Honkers 2025-5-29 02:27:43 来自手机 | 显示全部楼层 |阅读模式
<h2>1 关于回调函数</h2>
<h3>1.1 定义</h3>
<p>回调函数的定义&#xff0c;可以很严&#xff08;复&#xff09;谨&#xff08;杂&#xff09;&#xff0c;也可以很简&#xff08;随&#xff09;单&#xff08;意&#xff09;。其实与其研究定义&#xff0c;还不如讨论为什么需要回调函数&#xff0c;回调函数能干些啥。<br /> 在我看来&#xff0c;回调函数不是在函数的定义上区别于普通函数&#xff0c;而是在调用的方式有区别&#xff0c;因为归根到底&#xff0c;他们都是代码中芸芸众生的普普通通的函数&#xff0c;即“回调函数”的重点在“回调”这两个字。<br /> 以花钱为例&#xff0c;花钱买衣服叫消费&#xff0c;花钱买股票叫投资&#xff0c;都是花钱&#xff0c;但是方式不同&#xff0c;意义也不同。<br /> ​</p>
<p>下图列举了普通函数执行和回调函数调用的区别。</p>
<ul><li>对于普通函数&#xff0c;就是按照实现设定的逻辑和顺序执行。</li><li>对于回调函数&#xff0c;假设Program A和Program B分别有两个人独立开发。回调函数Fun A2它是由Program A定义&#xff0c;但是由Program B调用。Program B只负责取调用Fun A2&#xff0c;但是不管Fun A2函数具体的功能实现。<br />
<img src="https://i-blog.csdnimg.cn/blog_migrate/585143d1d7f317e727d37e45d084b4f9.png" alt="image.png" />
<br /> ​</li></ul>
<h3>1.2 为什么需要回调函数</h3>
<p>因为有这样的使用场景&#xff0c;Fun A2只有在 Fun B1调用时才能执行&#xff0c;有点像中断函数的概念。那可能会有人问&#xff0c;在Program A中不断去查询Fun B1的状态&#xff0c;一旦它被执行了&#xff0c;就让Program A自己去执行Fun A2不行吗&#xff1f;如果你有这样的疑问&#xff0c;说明你已经入门了。<br /> 答案是“可以”&#xff0c;但是这样实现的方案不好。因为整个过程中Program A一直都在查询状态&#xff0c;非常耗资源&#xff0c;查询频率高了费CPU&#xff0c;查询频率低了实时性保证不了&#xff0c;Fun B1都执行好久了你才反应过来&#xff0c;Fun A2的执行就会明显晚于Fun B1了。<br /> 正因为如此&#xff0c;回调函数才登上了舞台。<br /> ​</p>
<p>如果依然一知半解&#xff0c;可以看这位知乎答主的回答。回调函数&#xff08;callback&#xff09;是什么&#xff1f;<br /> ​</p>
<h2>2 如何实现函数回调</h2>
<p>函数的回调并不复杂&#xff0c;把 Fun A2的函数的地址/指针告诉Program B就可以了。<br /> 其实我们在这里要讨论的是在C&#43;&#43;中&#xff0c;常用回调函数的类型。<br />
<img src="https://i-blog.csdnimg.cn/blog_migrate/7bb0b1bb65133e715b463675362e84a6.png#pic_center" alt="image.png" />
<br /> ​</p>
<p>得到函数的地址是其中一个关键步骤。<br /> 普通和函数和类的静态成员函数都分配有确定的函数地址&#xff0c;但是类的普通函数是类共用的&#xff0c;并不会给类的每一个实例都分配一个独立的成员函数&#xff0c;这样就太浪费存储资源了。所以类的非静态函数作为回调函数是有区别的&#xff0c;也是这篇文章想要讨论的重点。</p>
<p>不过我们还以一步一步来&#xff0c;从简单的开始。</p>
<h3>2.1 普通函数作为回调函数</h3>
  1. #include &lt;iostream&gt;
  2. void programA_FunA1() { printf(&#34;I&#39;am ProgramA_FunA1 and be called..\n&#34;); }
  3. void programA_FunA2() { printf(&#34;I&#39;am ProgramA_FunA2 and be called..\n&#34;); }
  4. void programB_FunB1(void (*callback)()) {
  5.   printf(&#34;I&#39;am programB_FunB1 and be called..\n&#34;);
  6.   callback();
  7. }
  8. int main(int argc, char **argv) {
  9.   programA_FunA1();
  10.   programB_FunB1(programA_FunA2);
  11. }
复制代码

<p>执行结果&#xff1a;<br />
<img src="https://i-blog.csdnimg.cn/blog_migrate/3dc89ee9b2d02d8e962e5f0fc862a731.png" alt="image.png" />
<br /> 没有什么可说的&#xff0c;非常简单。</p>
<h3>2.2 类的静态函数作为回调函数</h3>
  1. #include &lt;iostream&gt;
  2. class ProgramA {
  3. public:
  4.   void FunA1() { printf(&#34;I&#39;am ProgramA.FunA1() and be called..\n&#34;); }
  5.   static void FunA2() { printf(&#34;I&#39;am ProgramA.FunA2() and be called..\n&#34;); }
  6. };
  7. class ProgramB {
  8. public:
  9.   void FunB1(void (*callback)()) {
  10.     printf(&#34;I&#39;am ProgramB.FunB1() and be called..\n&#34;);
  11.     callback();
  12.   }
  13. };
  14. int main(int argc, char **argv) {
  15.   ProgramA PA;
  16.   PA.FunA1();
  17.   ProgramB PB;
  18.   PB.FunB1(ProgramA::FunA2);
  19. }
复制代码

<p>执行结果<br />
<img src="https://i-blog.csdnimg.cn/blog_migrate/0b9501ab462b68d1895e99dcccdcf9b6.png" alt="image.png" />
</p>
<p>可以看出&#xff0c;以上两种方式没有什么本质的区别。<br /> 但这种实现有一个很明显的缺点&#xff1a;static 函数不能访问非static 成员变量或函数&#xff0c;会严重限制回调函数可以实现的功能。</p>
<h3>2.3 类的非静态函数作为回调函数</h3>
  1. #include &lt;iostream&gt;
  2. class ProgramA {
  3. public:
  4.   void FunA1() { printf(&#34;I&#39;am ProgramA.FunA1() and be called..\n&#34;); }
  5.   void FunA2() { printf(&#34;I&#39;am ProgramA.FunA2() and be called..\n&#34;); }
  6. };
  7. class ProgramB {
  8. public:
  9.   void FunB1(void (ProgramA::*callback)(), void *context) {
  10.     printf(&#34;I&#39;am ProgramB.FunB1() and be called..\n&#34;);
  11.     ((ProgramA *)context-&gt;*callback)();
  12.   }
  13. };
  14. int main(int argc, char **argv) {
  15.   ProgramA PA;
  16.   PA.FunA1();
  17.   ProgramB PB;
  18.   PB.FunB1(&amp;ProgramA::FunA2, &amp;PA);  // 此处都要加&amp;
  19. }
复制代码

<p>执行结果<br />
<img src="https://i-blog.csdnimg.cn/blog_migrate/0b9501ab462b68d1895e99dcccdcf9b6.png" alt="image.png" />
<br /> 这种方法可以得到预期的结果&#xff0c;看似完美&#xff0c;但是也存在明显不足。<br /> 比如在programB中FunB1还使用 programA的类型&#xff0c;也就我预先还要知道回调函数所属的类定义&#xff0c;当programB想独立封装时就不好用了。<br /> ​</p>
<p>这里还有一种方法可以避免这样的问题&#xff0c;可以把非static的回调函数 包装为另一个static函数&#xff0c;这种方式也是一种应用比较广的方法。</p>
  1. #include &lt;iostream&gt;
  2. class ProgramA {
  3. public:
  4.   void FunA1() { printf(&#34;I&#39;am ProgramA.FunA1() and be called..\n&#34;); }
  5.   void FunA2() { printf(&#34;I&#39;am ProgramA.FunA2() and be called..\n&#34;); }
  6.   static void FunA2Wrapper(void *context) {
  7.     printf(&#34;I&#39;am ProgramA.FunA2Wrapper() and be called..\n&#34;);
  8.     ((ProgramA *)context)-&gt;FunA2();  // 此处调用的FunA2()是context的函数, 不是this-&gt;FunA2()
  9.   }
  10. };
  11. class ProgramB {
  12. public:
  13.   void FunB1(void (ProgramA::*callback)(), void *context) {
  14.     printf(&#34;I&#39;am ProgramB.FunB1() and be called..\n&#34;);
  15.     ((ProgramA *)context-&gt;*callback)();
  16.   }
  17.   void FunB2(void (*callback)(void *), void *context) {
  18.     printf(&#34;I&#39;am ProgramB.FunB2() and be called..\n&#34;);
  19.     callback(context);
  20.   }
  21. };
  22. int main(int argc, char **argv) {
  23.   ProgramA PA;
  24.   PA.FunA1();
  25.   ProgramB PB;
  26.   PB.FunB1(&amp;ProgramA::FunA2, &amp;PA);  // 此处都要加&amp;
  27.   printf(&#34;\n&#34;);
  28.   PB.FunB2(ProgramA::FunA2Wrapper, &amp;PA);
  29. }
复制代码

<p>执行结果&#xff1a;<br />
<img src="https://i-blog.csdnimg.cn/blog_migrate/7d539e0833af4eec31c4f70e9bb3f36c.png" alt="image.png" />
<br /> 这种方法相对于上一种&#xff0c;ProgramB中没有ProgramA的任何信息了&#xff0c;是一种更独立的实现方式。<br /> FunB2()通过调用FunA2Wrapper()&#xff0c;实现间接的对FunA2()的调用。FunA2()可以访问和调用对类内的任何函数和变量。多了一个wrapper函数&#xff0c;也多了一些灵活性。<br /> ​</p>
<p>上面借助wrapper函数实现回调&#xff0c;虽然很灵活&#xff0c;但是还是不够优秀&#xff0c;比如&#xff1a;<br /> 1&#xff09;多了一个不是太有实际用处的wrapper函数。<br /> 2&#xff09;wrapper中还要对传入的指针进行强制转换。<br /> 3&#xff09;FunB2调用时&#xff0c;不但要指定wrapper函数的地址&#xff0c;还要传入PA的地址。<br /> ​</p>
<p>那是否有更灵活、直接的方式呢&#xff1f;有&#xff0c;可以继续往下看。</p>
<h2>3 std::funtion和std::bind的使用</h2>
<p>std::funtion和std::bind可以登场了。<br /> std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作&#xff0c;这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等[1]。<br /> std::bind()函数的意义就像它的函数名一样&#xff0c;是用来绑定函数调用的某些参数的[2]。<br /> 关于他们的详细用法可以自行百度&#xff0c;如果有需要的以后出一期单独写&#xff0c;这里直接上代码&#xff0c;看std::funtion和std::bind如何在回调中使用。<br /> ​</p>
  1. #include &lt;iostream&gt;
  2. #include &lt;functional&gt; // fucntion/bind
  3. class ProgramA {
  4. public:
  5.   void FunA1() { printf(&#34;I&#39;am ProgramA.FunA1() and be called..\n&#34;); }
  6.   void FunA2() { printf(&#34;I&#39;am ProgramA.FunA2() and be called..\n&#34;); }
  7.   static void FunA3() { printf(&#34;I&#39;am ProgramA.FunA3() and be called..\n&#34;); }
  8. };
  9. class ProgramB {
  10.   typedef std::function&lt;void ()&gt; CallbackFun;
  11. public:
  12.    void FunB1(CallbackFun callback) {
  13.     printf(&#34;I&#39;am ProgramB.FunB2() and be called..\n&#34;);
  14.     callback();
  15.   }
  16. };
  17. void normFun() { printf(&#34;I&#39;am normFun() and be called..\n&#34;); }
  18. int main(int argc, char **argv) {
  19.   ProgramA PA;
  20.   PA.FunA1();
  21.   printf(&#34;\n&#34;);
  22.   ProgramB PB;
  23.   PB.FunB1(normFun);
  24.   printf(&#34;\n&#34;);
  25.   PB.FunB1(ProgramA::FunA3);
  26.   printf(&#34;\n&#34;);
  27.   PB.FunB1(std::bind(&amp;ProgramA::FunA2, &amp;PA));
  28. }
复制代码

<p>执行输出&#xff1a;<br />
<img src="https://i-blog.csdnimg.cn/blog_migrate/30b195b645e32065a4d5a123ce7e94bd.png" alt="image.png" />
<br /> std::funtion支持直接传入函数地址&#xff0c;或者通过std::bind指定。<br /> 简而言之&#xff0c;std::funtion是定义函数类型(输入、输出)&#xff0c;std::bind是绑定特定的函数&#xff08;具体的要调用的函数&#xff09;。<br /> ​</p>
<p>相比于wrapper方法&#xff0c;这个方式要更直接、简洁很多。</p>
<h2>Ref:</h2>
<p>[1] https://blog.csdn.net/u013654125/article/details/100140328<br /> [2] https://blog.csdn.net/u013654125/article/details/100140547</p>
<p>​</p>
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Honkers

荣誉红客

关注
  • 4014
    主题
  • 36
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行