如何在非窗口类中使用定时器?
如何在非窗口类中使用定时器?
非窗口类中使用定时器的方法
作者:刘辉
(网进科技 江苏 昆山 Email:jemmyliu@163.com 完成时间:2003年3月12日)
摘 要 本文主要通过一些简单的例子,介绍了如何在Visual C++的窗口和非窗口类中使用定时器。重点介绍了如何用静态成员函数和静态数据成员在非窗口类中使用定时器,同时,又介绍了与定时器相关的知识,例如回调函数,C++类中的静态成员,以及模板类中的映射类等。
关键字 C++ 类 定时器 静态函数静态成员函数 静态数据成员 回调函数 映射类
摘 要:This page introduce how to use timer in window class and none window class of Visual C++ by some simple samples. Use timer in none window class with static member variable and static member function is the important point. At the same time, it also tell about of some knowledge such as about timer, callback function, static member of C++ class and map class CMap of template class.
关键字:C++ Class Timer static CALLBACK CMap
1. 引言
定时器在Windows 的程序中的作用不可忽略,也随处可见。设定一个时间间隔每0.5秒或者1秒钟刷新一次时钟,这样就可以完成一个简单的电子钟程序。在不同的编程工具中定时器的用法也不同,Visual C++中也给我们提供了实现这种功能的方法,而且方法不只一种。在窗口类中是使用定时器比较很简单,用SetTimer()设置了定时器之后,并在Class Wizard中添加了OnTimer消息映射后,您就可以在映射函数OnTimer()中添加代码实现,来定时完成您的任务,而且还支持任意多个定时器,这种方法大家可能都会用。但是在非窗口的类中,使用定时器就没那么简单了,在类消息映射中就找不到OnTimer()方法了,类中也没有hWnd这个属性,SetTimer()也不能象原来那样使用了,下面给出了一种既不破坏类的完整性的同时又能巧妙的使用定时器的方法。
2. 相关知识
在非窗口类中使用定时器,需要了解的知识比较多。首先非窗口类中没有消息映射,也没有象CWnd类具有的SetTimer()方法来设置定时器。没有消息映射,就只能靠我们自己定义的回调函数来处理定时器的消息,因此大家有必要了解一下回调函数的概念。因为回调函数只能用全局函数或者静态成员函数来实现,为了维持类的完整性,使用类的静态成员函数来作为回调函数,所以我们又需要了解一下静态数据成员和静态成员函数的性质。又因为定时器是在我们的程序中产生的,这又需要来管理定时器,所以又用到了映射表类CMap,因此介绍一下CMap的简单用法也是必不可少的。
2.1 回调函数
所谓回调函数就是按照一定的形式由你定义并编写实现内容,当发生某种事件时,而由系统或其它函数来调用的函数。
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己编写的一个函数(也就是回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,也就是某种事情发生的时候,利用传递的函数地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。回调函数只能是全局函数,或者是静态函数,因为这个函数只是在这个类中使用,所以为了维护类的完整性,我们用类的静态成员函数来做回调函数。
2.2 C++类中的静态成员
在C语言中,声明一个数据为静态类型,意味着该变量的生存周期是静态的,即在程序的开始时即分配,到程序终止时才释放。但在C++中,声明一个类中的成员为静态类型,则意味着该类的所有实例只有该成员的一个拷贝。也就是说,不管应用程序中创建了这个类的多少个对象,其静态成员只有一个副本,该副本为这个类的所有对象实例所共享,而对于非静态成员,每个类对象实例都有自己的拷贝。例如:
class CPerson
{
public:
CString szName;
static CString szCompanyName;
CPerson();
virtual ~CPerson();
};
接着用该类声明一个实例 CPerson me;
对于同一家公司员工,每个人都有不同的姓名,但是他们的公司名字是一样的,所以就可以用一个静态类型来保存,这样所有的员工都共享这个公司名称,只要一位员工更新了公司名称,则所有员工的公司名称就被更新了。
静态成员被当作该类类型的全局对象,可以把一个静态数据成员和静态成员函数当成全局变量和函数那样去存储和访问,但又被隐藏在类的内部,并且清楚地与这个类相联系但又不是全局对象,同全局对象相比,使用静态成员有两个优势:
(1) 静态成员没有进入程序的全局名字空间,它属于类,它的名字只在类的范围内有效,因此不存在与程序中其他全局名字冲突的可能性。
(2) 可以实现信息隐藏,并可以保持类的完整性,可以是private(私有的)成员、public(公有的)成员或者protected(保护的)成员,而全局对象不能。
2.2.1 静态数据成员
使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,就可以保证所有对象都能够访问到被更新后的值,这样可以提高效率和节省内存空间。
在类中将一个成员变量声明为静态的,与声明普通变量的唯一区别就是在其定义前加一个static。
象上面的例子中那样声明:
static CString szCompanyName。
静态数据成员显式初始化与一般数据成员初始化不同。静态数据成员显式初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值>
对于上面的例子这样初始化:
CString CPerson::szCommpanyName = "网进科技",
这表明:
(1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。
(2) 初始化时不加该成员的访问权限控制符private,public等。
(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。
在类的成员函数中可以直接引用该类的静态数据成员,而不必使用成员访问操作符。但是在非成员函数中,我们必须一两种方式之一访问静态数据成员。
(1) 使用成员访问操作符。
例如:me是CPerson的一个实例,在非成员函数中可以这样应用其中的静态数据成员:
CString TheCommpanyName = me.CommpanyName。
(2) 因为类静态数据成员只有一个拷贝,所以它不一定要通过对象或者指针来访问。方法二就是用被类名限定修饰的名字直接访问它。当我们不通过类的成员访问操作符访问静态数据成员时,必须指定类名以及紧跟其后的域操作符,因为静态成员不是全局对象,所以我们不能在全局域中找到它。如:
CString TheCommpanyName = CPerson::CommpanyName。
顺便说一句静态数据成员还有两个特点:
(1) 静态数据成员的类型可以是其所属类,而非静态数据成员只能被声明为该类的对象的指针或引用。
(2) 静态数据成员可以被作为类成员函数的缺省实参,而非静态成员不能。
2.2.1 静态成员函数
静态成员函数的声明与普通函数的唯一区别就是在前面加一个static。
通常,当前对象的地址(this)是被隐含地传递到被调用的非静态成员函数的。静态成员函数具有类的范围,同非静态成员函数相比,静态成员函数没有this参数,因此它不能访问一般的数据成员,而只能访问静态数据成员、枚举或嵌套类型和其他的静态成员函数。这样使用静态成员函数在速度上可以比全局函数有少许的增长,它不仅没有传递this指针所需的额外的花费,而且还有使函数在类内的好处。如果静态成员函数中要引用非静态成员时,可通过对象来引用。
我们可以用成员访问操作符点(.)和箭头(->)为一个类对象或指向类对象的指针访问静态成员函数,也可以用限定修饰名直接访问静态成员函数,而无需声明类对象。
静态成员函数遵循约束:
(1) 不能用成员选择符(.或->)访问非静态成员。
(2) 不能说明为虚函数。
(3) 不能与有相同参数类型的非静态成员函同名。
(4) 不能声明为const或volatile。
(5) 出现在类体外的函数定义不指定关键字static
2.3 映射表类CMap
映射表类(CMap)是MFC集合类中的一个模板类,也称作为“字典”,就像一种只有两列的表格,一列是关键字,一列是数据项,它们是一一对应的。关键字是唯一的,给出一个关键字,映射表类会很快找到对应的数据项。映射表的查找是以哈希表的方式进行的,因此在映射表中查找数值项的速度很快。举个例子来说吧,公司的所有职员都有一个工号和自己的姓名,工号就是姓名的关键字,给出一个工号,就可以很快的找到相应的姓名。映射类最适用于需要根据关键字进行快速检索的场合,我们的程序中就用映射表来保存计时器标志值和类实例指针,用计时器的标志值作为关键字。
3. 让静态成员函数也能访问非静态成员函数
从上面的叙述可以看出来,在类中静态成员函数只能引用静态数据成员和静态成员函数,如何才能让静态成员函数也能引用非静态的成员函数和成员变量呢?这也是我们后面将会用到的。
分析一下静态成员函数和非静态成员函数的区别,我们会发现非静态成员函数之所以能访问所有的成员函数和成员变量,是因为它有个隐含的参数this,访问成员函数和成员变量的时候,实际上是在前面添加了个引用的符号"this->",所以我们就可以试着将this这个指针作为静态成员函数的一个参数传递进去,这样不就可以在静态成员函数中访问所有的成员函数和成员变量了吗?下面给出一个实现的例子:
Person.h文件如下:
class CPerson
{
public:
//该实例的一句座右铭
CString szMotto;
//用于保存该实例的指针
CPerson* pThis;
//非静态成员函数,弹出该实例的座右铭
void GetMotto();
//静态成员函数,弹出该实例的座右铭
static void GetMottoStaic(CPerson* pPerson);
CPerson();
virtual ~CPerson();
};
Person.cpp文件如下:
#include "stdafx.h"
#include "Person.h"
CPerson::CPerson()
{
pThis = this;
}
CPerson::~CPerson()
{
}
void CPerson::GetMotto()
{
AfxMessageBox(szMotto);
}
void CPerson::GetMottoStaic(CPerson* pPerson)
{
pPerson->GetMotto();
}
在需要的地方就可以如下访问静态成员函数:
m_Person.szMotto = "我的座右铭是:这是由静态函数访问非静态函数的结果!";
m_Person.GetMottoStaic(m_Person.pThis);
其实这个例子实际上是没有什么意义的,这样做的目的只是为了演示如何实现这个方法而已。