一 生命周期
可以把程序看成一个生命体,也有出生、成长、结束的阶段,每个阶段都可能会有让外界来参与的需求(比如人生病了需要外界治疗,自身抵抗力不能完全自愈),如果有windows开发经验,很容易想起wm_create, wm_destory等消息。
或者移动端(安卓、ios)的程序起止事件响应,甚至页面切换的事件。在每个事件中,提供了一个中断(回调函数),可以和外界进行交互。最简单的例子就是程序启动时增加一个启动画面,很多软件在退出的时候弹出一个提示窗口或打开一个浏览器链接(这种做法非常不友好,不推荐)。需要在程序的相应位置提供一个可以由外面参与的机会。这时回调函数就派上用场了。
二 回调函数一般是使用c语言的方法,不过,c 因为自身的特点,可以使用更恰当的办法(不存在好坏),我们这里使用抽象基类做参数的方法来实现,这是一种很传统的方法了,比如com本质论这本书上第一章就讲解过。
(本文涉及到虚函数的一些知识,还有抽象基类,一般c 教材会讲到。)
本文在前文的代码上面增加回调机制。
以下先讲关键代码。
-
#include <iostream>// stl cout
-
#include <signal.h>//signal头文件
-
#include <chrono> //stl chrono头文件,时间工具,可以精确到,可以精确到纳秒
-
#include <thread> //stl thread
-
-
#include "include/glog/logging.h"
-
#pragma comment(lib, "lib/release/glog.lib")
-
using namespace google;
-
-
class iappcb
-
{
-
public:
-
virtual int oninit() = 0;
-
virtual int ondestroy() = 0;
-
};
-
-
class appcbimpl :public iappcb
-
{
-
public:
-
virtual int oninit()
-
{
-
std::cout << "oninit\n";
-
-
return 0;
-
};
-
virtual int ondestroy()
-
{
-
std::cout << "ondestroy\n";
-
return 0;
-
};
-
};
-
-
-
static int signaled = 0;
-
static void sigterm_handler(int sig)
-
{
-
signaled = 1;
-
}
-
-
class app
-
{
-
public:
-
void run()
-
{
-
//std::chrono::milliseconds可以是以下四个中的一个:seconds,milliseconds,microseconds,nanoseconds
-
uint64_t start_millseconds = std::chrono::duration_cast<std::chrono::milliseconds>
-
(std::chrono::system_clock::now().time_since_epoch()).count();
-
for (;;)
-
{
-
if (signaled == 1)
-
break;
-
else
-
{
-
std::cout << "运行毫秒数:" << std::chrono::duration_cast<std::chrono::milliseconds>
-
(std::chrono::system_clock::now().time_since_epoch()).count() - start_millseconds << std::endl;
-
-
//c11支持u8转utf8,否则写到文件里乱码
-
log(info) << u8"运行毫秒数:" << std::chrono::duration_cast<std::chrono::milliseconds>
-
(std::chrono::system_clock::now().time_since_epoch()).count() - start_millseconds ;
-
std::this_thread::sleep_for(std::chrono::seconds(1));
-
}
-
}
-
};
-
};
-
-
iappcb* m_pcb = nullptr;
-
int main()
-
{
-
m_pcb = new appcbimpl;
-
google::initgooglelogging("demo3.exe");//
-
google::setlogdestination(google::glog_info, "demo3_");
-
log(info) << "hello world!";
-
std::cout << "hello world!\n";
-
if (m_pcb->oninit() != 0)//程序启动时由外部确定需要执行哪些操作,比如显示欢迎页。如果程序初始化失败,oninit 非0数值,程序退出。
-
{
-
delete m_pcb;
-
m_pcb = nullptr;
-
return 0;
-
}
-
signal(sigint, sigterm_handler); //ctrl c中断
-
-
-
app app;
-
app.run();
-
std::cout << "exit!\n";
-
log(info) << "exit!";
-
m_pcb->ondestroy();//程序退出前由外部决定还要执行哪些操作,比如各种流氓软件退出时弹出页面。
-
-
std::this_thread::sleep_for(std::chrono::seconds(1));
-
google::shutdowngooglelogging();
-
-
delete m_pcb;
-
m_pcb = nullptr;
-
return 0;
-
}
这个代码主要是在前文的基础上增加了两个class,一个是抽象基类:iappcb,这个名字的意思是 app的回调函数接口。一般class名字第一个字母用i 表示是接口(intereface),写过com的朋友很清楚。最后两个字母cb表示回调(callback),使用过微软sdk的朋友也有感觉,比如directshow里面大量的cb类。
appcbimpl继承
iappcb,名字带impl,如果了解设计模式会知道这是其中桥接模式(bridge),写java代码中会经常使用。
这两个类,
iappcb声明接口,appcbimpl负责实现,标准的面象对象设计方法。因为c 首先是兼容c,所以一般初学都没有这方面意识,大多还是面向过程的方式来写代码。而java是重新设计的,最初就采用了这种模式,可能好多人还不了解什么是设计模式,就照葫芦画瓢模仿着写,所以感觉java学起来比c 容易。其实只要理解了里面的知识点,用哪种语言写起来都一样。
继续来,iappcb* m_pcb = nullptr;
然后再代码里m_pcb = new appcbimpl;
这样写还是面向过程,不是完全的面向对象。不过还不急,这部分还相当于伪代码,演示作用,后续会慢慢纠正。
本文重点是增加了两处:
第一处:if (m_pcb->oninit() != 0)
代码的注释里也写了,这相当于一般程序的启动时,通知外界,是否需要干涉。一般可以在这个函数里做一些初始化的动作。
第二处:
m_pcb->ondestroy();
这是程序退出时要执行的操作。比如一般程序在退出时保存当前执行状态,关键的参数等。
回调函数除了程序本身用以外,还可以对外开放。比如程序本身封成sdk,源码不可能,但提供 还
iappcb的接口,调用着可以自己定义一个子类执行具体参数,然后把接口声音的变量传到主程序中,可以自己控制程序的执行。
本文完整代码在
与文中的一致。
阅读(1906) | 评论(0) | 转发(0) |