非視窗類別中使用定時器的方法及相關知識的介紹

來源:互聯網
上載者:User
本文代碼運行如下:
 

摘 要:本文主要通過一些簡單的例子,介紹了如何在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

一、引言

定時器在Windows 的程式中的作用不可忽略,也隨處可見。設定一個時間間隔每0.5秒或者1秒鐘重新整理一次時鐘,這樣就可以完成一個簡單的電子鐘程式。在不同的編程工具中定時器的用法也不同,Visual C++中也給我們提供了實現這種功能的方法,而且方法不只一種。在視窗類別中是使用定時器比較很簡單,用SetTimer()設定了定時器之後,並在Class Wizard中添加了OnTimer訊息映射後,您就可以在映射函數OnTimer()中添加代碼實現,來定時完成您的任務,而且還支援任意多個定時器,這種方法大家可能都會用。但是在非視窗的類中,使用定時器就沒那麼簡單了,在類訊息映射中就找不到OnTimer()方法了,類中也沒有hWnd這個屬性,SetTimer()也不能象原來那樣使用了,下面給出了一種既不破壞類的完整性的同時又能巧妙的使用定時器的方法。

二、相關知識

在非視窗類別中使用定時器,需要瞭解的知識比較多。首先非視窗類別中沒有訊息映射,也沒有象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.2 靜態成員函數
靜態成員函數的聲明與普通函數的唯一區別就是在前面加一個static。
通常,當前對象的地址(this)是被隱含地傳遞到被調用的非靜態成員函數的。靜態成員函數具有類的範圍,同非靜態成員函數相比,靜態成員函數沒有this參數,因此它不能訪問一般的資料成員,而只能訪問待用資料成員、枚舉或巢狀型別和其他的靜態成員函數。這樣使用靜態成員函數在速度上可以比全域函數有少許的增長,它不僅沒有傳遞this指標所需的額外的花費,而且還有使函數在類內的好處。如果靜態成員函數中要引用非靜態成員時,可通過對象來引用。
我們可以用成員訪問操作符點(.)和箭頭(->)為一個類對象或指向類對象的指標訪問靜態成員函數,也可以用限定修飾名直接存取靜態成員函數,而無需聲明類對象。
靜態成員函數遵循約束:
(1) 不能用成員選擇符(.或->)訪問非靜態成員。
(2) 不能說明為虛函數。
(3) 不能與有相同參數類型的非靜態成員函同名。
(4) 不能聲明為const或volatile。
(5) 出現在類體外的函數定義不指定關鍵字static

2.3 映射表類CMap
映射表類(CMap)是MFC集合類中的一個模板類,也稱作為“字典”,就像一種只有兩列的表格,一列是關鍵字,一列是資料項目,它們是一一對應的。關鍵字是唯一的,給出一個關鍵字,映射表類會很快找到對應的資料項目。映射表的尋找是以雜湊表的方式進行的,因此在映射表中尋找數值項的速度很快。舉個例子來說吧,公司的所有職員都有一個工號和自己的姓名,工號就是姓名的關鍵字,給出一個工號,就可以很快的找到相應的姓名。映射類最適用於需要根據關鍵字進行快速檢索的場合,我們的程式中就用映射表來儲存計時器標誌值和類執行個體指標,用計時器的標誌值作為關鍵字。

三、讓靜態成員函數也能訪問非靜態成員函數

從上面的敘述可以看出來,在類中靜態成員函數只能引用待用資料成員和靜態成員函數,如何才能讓靜態成員函數也能引用非靜態成員函數和成員變數呢?這也是我們後面將會用到的。
分析一下靜態成員函數和非靜態成員函數的區別,我們會發現非靜態成員函數之所以能訪問所有的成員函數和成員變數,是因為它有個隱含的參數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);

其實這個例子在實際上是沒有什麼意義的,這樣做的目的只是為了示範如何?這個方法而已。

四、使用定時器

Windows提供了定時器,協助我們編寫定期發送訊息的程式。定時器一般通過一下兩中方式通知應用程式間隔時間已到。
⑴ 給指定視窗發送WM_TIMER訊息,也就是下面的給出在視窗類別中使用的方法。
⑵ 調用一個應用程式定義的回呼函數,也就是在非視窗類別中使用方法。

4.1 在視窗類別中使用定時器
在視窗類別中使用定時器比較簡單。假如我們想讓這個視窗上放置一個電子鐘,這樣我們必須每1秒或者0.5秒鐘去更新顯示顯見。按照下面的步驟,就可以完成這個電子鐘程式,並且知道如何在視窗類別中使用定時器:
首先做在我們建立項目的主視窗上添加一個Label控制項,用來顯示時間。接著
⑴ 用函數SetTimer設定一個定時器,函數格式如下:

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD));

這個函數是CWnd類的一個成員函數,其參數意義如下:

nIDEvent: 為設定的定時器指定的定時器標誌值,設定多個定時器的時候,每個定時器的值都不同,訊息處理函數就是通過這個參數來判斷是哪個定時器的。這裡我們設定為1。
nElapse: 指定發送訊息的時間間隔,單位是毫秒。這裡我們設定為1000,也就是一秒。
lpfnTimer: 指定定時器訊息由哪個回呼函數來執行,如果為空白,WM_TIMER將加入到應用程式的訊息佇列中,並由CWnd類來處理。這裡我們設定為NULL。
最後代碼如下:

SetTimer(1,1000,NULL);

⑵ 通過Class Wizard給主視窗類添加一個WM_TIMER訊息的映射函數,預設為OnTimer(UINT nIDEvent)。
⑶ 然後我們就可以在OnTimer(UINT nIDEvent)的函數實現中添加我們的代碼了。參數nIDEvent就是我們先前設定定時器時指定的標誌值,在這裡我們就可以通過它來區別不同的定時器,而作出不同的處理。添加的代碼如下:

switch(nIDEvent){case 1:CTime m_SysTime = CTime::GetCurrentTime();SetDlgItemText(IDC_STATIC_TIME,m_SysTime.Format("%Y年%m月%d日 %H:%M:%S"));break;}

代碼中的IDC_STATIC_TIME就是我們先前添加的Label控制項的ID。
至此,我們的電子鐘的程式就完成了。

4.2 在非視窗類別中使用定時器
在非視窗類別中使用定時器就要用到前面我們介紹到的所有知識了。因為是無視窗類別,所以我們不能使用在視窗類別中用訊息映射的方法來設定定時器,這時候就必須要用到回呼函數。又因為回呼函數是具有一定格式的,它的參數不能由我們自己來決定,所以我們沒辦法利用參數將this傳遞進去。可是靜態成員函數是可以訪問靜態成員變數的,因此我們可以把this儲存在一個靜態成員變數中,在靜態成員函數中就可以使用該指標,對於只有一個執行個體的指標,這種方法還是行的通的,由於在一個類中該靜態成員變數只有一個拷貝,對於有多個執行個體的類,我們就不能用區分了。解決的辦法就是把定時器標誌值作為關鍵字,類執行個體的指標作為項,儲存在一個靜態映射表中,因為是標誌值是唯一的,用它就可以快速檢索出映射表中對應的該執行個體的指標,因為是靜態,所以回呼函數是可以訪問他們的。

首先介紹一下用於設定定時的函數:

UINT SetTimer(HWND hWnd,              // handle of window for timer messagesUINT nIDEvent,          // timer identifierUINT uElapse,           // time-out valueTIMERPROC lpTimerFunc   // address of timer procedure);

其中的參數意義如下:
hWnd: 指定與定時器相關聯的視窗的控制代碼。這裡我們設為NULL。
nIDEvent: 定時器標誌值,如果hWnd參數為NULL,它將會被跳過,所以我們也設定為NULL。
uElapse: 指定發送訊息的時間間隔,單位是毫秒。這裡我們不指定,用參數傳入。
lpTimerFunc: 指定當間隔時間到的時候被統治的函數的地址,也就是這裡的回呼函數。這個函數的格式必須為以下格式:

VOID CALLBACK TimerProc(HWND hwnd,     // handle of window for timer messagesUINT uMsg,     // WM_TIMER messageUINT idEvent,  // timer identifierDWORD dwTime   // current system time);

其中的參數意義如下:
hwnd: 與定時器相關聯的視窗的控制代碼。
uMsg: WM_TIMER訊息。
idEvent: 定時器標誌值。
deTime: 系統啟動後所以經過的時間,單位毫秒。
最後設定定時器的代碼為:

m_nTimerID = SetTimer(NULL,NULL,nElapse,MyTimerProc);

先通過Class Wizard建立一個非視窗類別,選擇Generic Class類類型,類名稱為CMyTimer,該類的作用是每隔一段時間提醒我們做某件事情,然後用這個類建立三個執行個體,每個執行個體以不同的時間間隔提醒我們做不同的事情。
MyTimer.h

#include <afxtempl.h>class CMyTimer;//用模板類中的映射表類定義一種資料類型typedef CMap<UINT,UINT,CMyTimer*,CMyTimer*> CTimerMap;class CMyTimer  {public://設定定時器,nElapse表示時間間隔,sz表示要提示的內容void SetMyTimer(UINT nElapse,CString sz);//銷毀該執行個體的定時器void KillMyTimer();//儲存該執行個體的定時器標誌值UINT m_nTimerID;//待用資料成員要提示的內容CString szContent;//聲明待用資料成員,映射表類,用於儲存所有的定時器資訊static CTimerMap m_sTimeMap;//靜態成員函數,用於處理定時器的訊息static void CALLBACK MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime);CMyTimer();virtual ~CMyTimer();};

MyTimer.cpp

#include "stdafx.h"#include "MyTimer.h"//必須要在外部定義一下待用資料成員CTimerMap CMyTimer::m_sTimeMap;CMyTimer::CMyTimer(){m_nTimerID = 0;}CMyTimer::~CMyTimer(){}void CALLBACK CMyTimer::MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime){CString sz;sz.Format("%d號定時器:%s",idEvent,m_sTimeMap[idEvent]->szContent);AfxMessageBox(sz);}void CMyTimer::SetMyTimer(UINT nElapse,CString sz){szContent = sz;m_nTimerID = SetTimer(NULL,NULL,nElapse,MyTimerProc);m_sTimeMap[m_nTimerID] = this;}void CMyTimer::KillMyTimer(){KillTimer(NULL,m_nTimerID);m_sTimeMap.RemoveKey(m_nTimerID);}

這樣就完成了在非視窗類別中使用定時器的方法。以上這些代碼都在Windwos 2000 Professional 和 Visual C++ 6.0中編譯通過。

五、結論

通過以上的介紹,大家應該知道如何在靜態成員函數中訪問非待用資料成員和非靜態成員函數,並瞭解了如何在非視窗類別中使用定時器。當然這隻是解決這個問題的一種方法,相信還有更好的解決辦法。這個種方法有一定的靈活性,可以在很多地方用到,例如網路程式中的連線逾時以及定時重新整理等需要自己來控制,就可以使用這種方法。

http://www.vckbase.com/document/viewdoc/?id=594

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.