一 生命周期
可以把程序看成一个生命体,也有出生、成长、结束的阶段,每个阶段都可能会有让外界来参与的需求(比如人生病了需要外界治疗,自身抵抗力不能完全自愈),如果有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;
-
-
//c++11支持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的接口,调用着可以自己定义一个子类执行具体参数,然后把接口声音的变量传到主程序中,可以自己控制程序的执行。
本文完整代码在
与文中的一致。
阅读(2127) | 评论(0) | 转发(0) |