深入淺出MFC 第一篇(讀書筆記(概要))

MFC 是微軟的一個Application Frame,看網上好多人都評論說是 深入淺出MFC 是一本 比較經典的書 ,因此拿來拜讀一下。

************************************分割線,不喜歡說廢話。*************************************************************************************************************************

首先,今天是2017年6月15日,剛剛讀完了第一章,一個是爲了方便想看此書的朋友能夠更加方便的瞭解此書的大概內容。。也是爲了自己以後的溫故而知新。好了,開始。。。

深入淺出 MFC

第一章 ———— 勿在浮沙築高臺

第一章 Win32基本程序觀念

微笑

學習MFC 之前,必要的基礎是,對於Windows 程序的事件驅動特性的瞭解(包括消息的產生、獲得、分派、判斷、處理),以及對C++ 多態(polymorphism)的精確體

會。

所謂UI 資源是指功能菜單、對話框外貌、程序圖標、光標形狀等等東西。程序員必須在一個所謂的資源描述檔(.rc)中描述它們。RC 編譯器(RC.EXE)讀取RC 檔的描述後將所有UI資源檔集中製作出一個.RES 檔,再與程序代碼結合在一起,這纔是一個完整的Windows可執行檔。

應用程序所調用的Windows API 函數是在「執行時期」才聯結上的。事實上.exe、.dll、.fon、.mod、.drv、.ocx 都是所謂的動態聯結函數庫。

微笑以消息爲基礎,以事件驅動之 ( message based ,  event driven )

Windows 程序的進行系依靠外部發生的事件來驅動。程序不斷等待(利用一個while 迴路),等待任何可能的輸入,然後做判斷,然後再做適當的處理。

以消息形式(一種數據結構)進入程序之中。由硬件裝置所產生的消息(如鼠標移動或鍵盤被按下),放在系統隊列(system queue)中,以及由Windows 系統或其它
Windows 程序傳送過來的消息,放在程序隊列(application queue)中。以應用程序的眼光來看,消息就是消息,來自哪裏或放在哪裏其實並沒有太大區別,反正程序調用
GetMessage API 就取得一個消息,程序的生命靠它來推動。所有的GUI 系統,包括UNIX的X Window 以及OS/2 的Presentation Manager,都像這樣,是以消息爲基礎的事件

驅動系統。
可想而知,每一個Windows 程序都應該有一個迴路如下:

MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 以上出現的函數都是Windows API 函數消息,也就是上面出現的MSG 結構,其實是Windows 內定的一種資料格式:
/* Queued message structure */
typedef struct tagMSG
{
HWND hwnd;
UINT message; // WM_xxx,例如WM_MOUSEMOVE,WM_SIZE...
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;


第二章 C++的重要性

C++ 是一種扭轉程序員思維模式的語言。一個人思維模式的扭轉,不可能輕而易舉一蹴而成。

----作者語

對象導向程序語言(Object Oriented Programming Language)是專門爲對象導向觀念而發展出來的,以之
完成對象導向的封裝、繼承、多態等特性自是最爲便利。

所謂純對象導向語言,是指不管什麼東西,都應該存在於對象之中。JAVA 和Small Talk都是純對象導向語言。

從類別與對象的關係開始,逐步解釋封裝、繼承、多態、虛擬函數、動態綁定。

類別及其成員 -  談封裝 ( encapsulation )

將一類事物共有的屬性放置在一起的一個總和就是類。類定義的變量叫 類對象,類對象有 成員 和 方法。

如果我以CSquare 代表「四方形」這種類別,四方形有color,四方形可以display。好,color 就是一種成員變量,

display 就是一種成員函數:


CSquare square; // 聲明square 是一個四方形。
square.color = RED; // 設定成員變量。RED 代表一個顏色值。
square.display(); // 調用成員函數。
下面是C++ 語言對於CSquare 的描述:
class CSquare // 常常我們以 C  作爲類別名稱的開頭
{
private:
int m_color; // 通常我們以 m_  作爲成員變量的名稱開頭
public:
void display() { ... }
void setcolor(int color) { m_color = color; }
};
成員變量可以只在類別內被處理,也可以開放給外界處理。

C++ 提供了private、public 和protected 三種修飾詞。一般而言成員變量儘量聲明爲private,成員函數則通常聲明爲public。上例的m_color 既然聲明爲private,我們勢必得

準備一個成員函數setcolor,

供外界設定顏色用。把資料聲明爲private,不允許外界隨意存取,只能透過特定的接口來操作,這就是對象導向的封裝(encapsulation)特性

基礎類別與衍生類別:談繼承 ( (Inheritance) )   

C++ 神祕而特有的性質其實在於繼承。人類習慣把相同的性質抽取出來,成立一個基礎類別(base class),再從中衍化出衍生類別(derived class)。

微笑這裏講繼承用下自己的理解吧

class CShape // 形狀

{

private:
int m_color;

public:
void setcolor(int color) { m_color = color; }

};

class CRect : public CShape // 矩形是一種形狀
{ // 它會繼承 m_color 和 setcolor()
public:
void display() { ... }
};

class CEllipse : public CShape // 橢圓形是一種形狀
{ // 它會繼承 m_color 和 setcolor()
public:
void display() { ... }
};

class CTriangle : public CShape // 三角形是一種形狀
{
// 它會繼承 m_color 和 setcolor()
public:
void display() { ... }
};

class CSquare : public CRect // 四方形是一種矩形
{
public:
void display() { ... }
};

class CCircle : public CEllipse // 圓形是一種橢圓形
{
public:
void display() { ... }
};

此時,定義一個基類的指針 CShape * pShape = NULL;

再定義一個派生類的指針   

再實例化兩個矩形類 CRect rect1, rect2;

實例化一個圓的類 CCircle circle;

讓該基類指針指向一個派生的類 pShape = & rect2;

此時,調用基類指向成員的方法的時候,只會調用基類的方法。

此時,調用派生類指針指向的成員的方法的時候,只會調用派生類的方法。

so,why???

因爲成員方法再調用的時候,都會有一個隱藏的this指針被串進去,所有,程序能夠識別出來到底是哪一個對象的方法被調用。

如果基類和派生類中有同名的函數,則要具體看調用的指針類型。指針是基類的則調用基類方法。指針是派生類的則調用派生類方法。

那如果我要用同一個基類指針去指向不同的成員,而分別去調用不同的成員方法要怎麼辦呢???答案是在基類中使用虛函數(其性質就是多態)。

我們以相同的指令卻喚起了不同的函數,這種性質稱爲Polymorphism,意思是"theability to assume many forms"(多態)。編譯器在編譯階段無法判斷出是調用哪個對象的函數 ,只有在程序執行的時候才能夠確認,這種現象就叫做 遲綁定 或者 動態綁定。

存在虛函數的類都有一個一維的虛函數表叫做虛表。類的對象有一個指向虛表開始的虛指針。虛表是和類對應的,虛表指針是和對象對應的。

基類中有虛函數,繼承的派生類也有這個虛函數,在派生類中重載了的話,再次用基類的指針去調用派生類的方法就會出現派生類的方法,派生類

如果沒有對基類的該虛函數進行重載的話,就會調用基類的方法。也就是說,派生類有的調派生類,派生類沒有的調基類的。

多繼承的情況下,先構造基類(按照繼承順序),再構造子類。析構的順序正好與構造時相反。

抽象類--------(含有純虛函數的類爲抽象類)

在基類中確定不了後期要實現的具體功能,只知道傳入的內容和返回的內容,意思只起到佔位的作用,必須在後期的繼承派生類中去實現的(如果不該寫,則繼承類也爲抽象類)。

抽象類不能夠實例化對象。

虛函數結論:

■ 如果你期望衍生類別重新定義一個成員函數,那麼你應該在基礎類別中把此函數設爲virtual。

■ 以單一指令喚起不同函數,這種性質稱爲Polymorphism,意思是"the ability to assume many forms",也就是多態。

■ 虛擬函數是C++ 語言的Polymorphism 性質以及動態綁定的關鍵。

■ 既然抽象類別中的虛擬函數不打算被調用,我們就不應該定義它,應該把它設爲純虛擬函數(在函數聲明之後加上"=0" 即可)。

■ 我們可以說,擁有純虛函數者爲抽象類別(abstract Class),以別於所謂的具象類別(concrete class)。

■ 抽象類別不能產生出對象實體,但是我們可以擁有指向抽象類別之指針,以便於操作抽象類別的各個衍生類別。

■ 虛擬函數衍生下去仍爲虛擬函數,而且可以省略virtual 關鍵詞。

類別與對象大解剖

動態綁定機制,在執行時期,根據虛擬函數表,做出了正確的選擇。

衍生對象不但繼承其基礎類別的成員,又有自己的成員。那麼所謂的upcasting(向上強制轉型): (CDocument)mydoc,將會造成對象的內容被切割(object
slicing):

微笑靜態成員(變量與函數)

class SavingAccount
{
private:
char m_name[40]; // 存戶姓名
char m_addr[60]; // 存戶地址
double m_total; // 存款額
double m_rate; // 利率
...
};

這家行庫採用浮動利率,每個帳戶的利息都是根據當天的掛牌利率來計算。這時候m_rate 就不適合成爲每個帳戶對象中的一筆資料,否則每天一開市,光把所有帳戶內容

叫出來,修改m_rate 的值,就花掉不少時間。m_rate 應該獨立在各對象之外,成爲類別獨一無二的資料。怎麼做?在m_rate 前面加上static 修飾詞即可:

class SavingAccount
{
private:
char m_name[40]; // 存戶姓名
char m_addr[60]; // 存戶地址
double m_total; // 存款額
static double m_rate; // 利率
...
};

static 成員變量不屬於對象的一部份,而是類別的一部份,所以程序可以在還沒有誕生任何對象的時候就處理此種成員變量。但首先你必須初始化它。不要把static 成員變量的初始化動作安排在類別的構造式中,因爲構造式可能一再被調用,而變量的初值卻只應該設定一次。也不要把初始化動作安排在頭文件中,因爲它可能會被包含許多地方,因此也就可能被執行許多次。你應該在實作檔中且類別以外的任何位置設定其初值。例如在 main  之中,或全域函數中,或任何函數之外:
double SavingAccount::m_rate = 0.0075; // 設立static 成員變量的初值
void main() { ... }
這麼做可曾考慮到m_rate 是個private 資料?沒關係,設定static 成員變量初值時,不受任何存取權限的束縛。

微笑C++ 程序的生與死(兼談構造式與析構式)

C++ 的new 運算子和C 的malloc 函數都是爲了配置內存,但前者比之後者的優點是,new 不但配置對象所需的內存空間時,同時會引發構造式的執行。

構造式即 構造函數(實例化對象時第一個被自動調用的函數), 析構式 即 析構函數(對象生命週期到了後會自動調用的函數)。

我的結論是:

1.對於全域對象(如本例之GlobalObject),程序一開始,其構造式就先被執行(比程序進入點更早);程序即將結束前其析構式被執行。MFC 程序就有這樣一個全域對象,通常以application object 稱呼之,你將在第6章看到它。

2.對於區域對象,當對象誕生時,其構造式被執行;當程序流程將離開該對象的存活範圍(以至於對象將毀滅),其析構式被執行。

3.對於靜態(static)對象,當對象誕生時其構造式被執行;當程序將結束時(此對象因而將遭致毀滅)其析構式才被執行,但比全域對象的析構式早一步執
行。

4.對於以new 方式產生出來的區域對象,當對象誕生時其構造式被執行。析構式則在對象被delete 時執行(上例程序未示範)。

微笑四種不同的對象生存方式 ( (in stack、in heap、global、local static) )

在C++ 中,有四種方法可以產生一個對象。

第一種方法是在堆棧(stack)之中產生它:

void MyFunc()
{
CFoo foo; // 在堆棧(stack)中產生foo 對象
...
}

    第二種方法是在堆積(heap)之中產生它:

void MyFunc()
{
...
CFoo* pFoo = new CFoo(); // 在堆(heap)中產生對象
}

    第三種方法是產生一個全域對象(同時也必然是個靜態對象):

CFoo foo; // 在任何函數範圍之外做此動作

    第四種方法是產生一個區域靜態對象:

void MyFunc()
{
static CFoo foo; // 在函數範圍(scope)之內的一個靜態對象
...
}

不論任何一種作法,C++ 都會產生一個針對CFoo 構造式的調用動作。

前兩種情況,C++在配置內存-- 來自堆棧(stack)或堆積(heap)-- 之後立刻產生一個隱藏的(你的原代碼中看不出來的)構造式調用。

第三種情況,由於對象實現於任何「函數活動範圍(function scope)」之外,顯然沒有地方來安置這樣一個構造式調用動作。第三種情況(靜態全域對象)的構造式調用動作必須靠startup 碼幫忙。startup 碼是更早於程序進入點(main 或WinMain)執行起來的碼,由C++ 編譯器提供,被聯結到你的程序中。編譯器編譯你的程序,發現一個靜態對象,它會把這個對象加到一個串行之中,編譯器不只是加上此靜態對象,它還加上一個指針,指向對象之構造式及其參數(如果有的話)。把控制權交給程序進入點(main 或WinMain)之前,startup 碼會快速在該串行上移動,調用所有登記有案的構造式並使用登記有案的參數,於是就初始化了你的靜態對象。
第四種情況(區域靜態對象)相當類似C 語言中的靜態區域變量,只會有一個實體(instance)產生,而且在固定的內存上(既不是stack 也不是heap)。它的構造式在
控制權第一次移轉到其聲明處(也就是在MyFunc 第一次被調用)時被調用。
微笑異常處理 ( Exception Handling)
C++ 導入了三個新的exception 保留字:

1. try。之後跟隨一段以{ } 圈出來的程序代碼,exception 可能在其中發生。

2. catch。之後跟隨一段以{ } 圈出來的程序代碼,那是exception 處理例程之所在。catch 應該緊跟在try 之後。

3. throw。這是一個指令,用來產生(拋出)一個exception。

下面是個實例 :

try {
// try block.
}

catch (char *p) {
printf("Caught a char* exception, value %s\n",p);

}

catch (double d) {
printf("Caught a numeric exception, value %g\n",d);

}

catch (...) { // catch anything
printf("Caught an unknown exception\n");

}

微笑Template

C++ 的template 有兩種,一種針對function,另一種針對class。

Template Functions

template <class T> T power(T base, int exponent);

或者寫成以下方式:

template <class T>

T power(T base, int exponent);

Template Classes

template <class T>

class CThree

{

public:

CThree(T t1, T t2, T t3);

T Min();

T Max();

    private:

T a, b, c;

};

語法還不至於太稀奇古怪,把T 看成是大家熟悉的int 或float 也就是了

微笑第三章   MFC 六大關鍵技術之仿真

MFC 類別階層

Frame1  範例程序

MFC.H

#include <iostream.h>

class CObject
{
public:
CObject::CObject() { cout << "CObject Constructor \n"; }
CObject::~CObject() { cout << "CObject Destructor \n"; }
};

class CCmdTarget : public CObject
{
public:
CCmdTarget::CCmdTarget() { cout << "CCmdTarget Constructor \n"; }
CCmdTarget::~CCmdTarget() { cout << "CCmdTarget Destructor \n"; }
};


class CWinThread : public CCmdTarget
{
public:
CWinThread::CWinThread() { cout << "CWinThread Constructor \n"; }
CWinThread::~CWinThread() { cout << "CWinThread Destructor \n"; }
};


class CWinApp : public CWinThread

{

public:

CWinApp* m_pCurrentWinApp;

public:

CWinApp::CWinApp() { m_pCurrentWinApp = this; cout << "CWinApp Constructor \n"; }

CWinApp::~CWinApp() { cout << "CWinApp Destructor \n"; }

};


class CDocument : public CCmdTarget

{

public:

CDocument::CDocument() { cout << "CDocument Constructor \n"; }

CDocument::~CDocument() { cout << "CDocument Destructor \n"; }

};


class CWnd : public CCmdTarget

{

public:

CWnd::CWnd() { cout << "CWnd Constructor \n"; }

CWnd::~CWnd() { cout << "CWnd Destructor \n"; }

};


class CFrameWnd : public CWnd

{

public:

CFrameWnd::CFrameWnd() { cout << "CFrameWnd Constructor \n"; }

CFrameWnd::~CFrameWnd() { cout << "CFrameWnd Destructor \n"; }

};


class CView : public CWnd

{

public:

CView::CView() { cout << "CView Constructor \n"; }

CView::~CView() { cout << "CView Destructor \n"; }

};

// global function

CWinApp* AfxGetApp();


MFC.CPP

#include "my.h" // 原本包含mfc.h 就好,但爲了CMyWinApp 的定義,所以..
extern CMyWinApp theApp;

CWinApp* AfxGetApp()

{

return theApp.m_pCurrentWinApp;

}


MY.H

#include <iostream.h>

#include "mfc.h"


class CMyWinApp : public CWinApp

{

public:

CMyWinApp::CMyWinApp() { cout << "CMyWinApp Constructor \n"; }

CMyWinApp::~CMyWinApp() { cout << "CMyWinApp Destructor \n"; }

};


class CMyFrameWnd : public CFrameWnd

{

public:

CMyFrameWnd() { cout << "CMyFrameWnd Constructor \n"; }

~CMyFrameWnd() { cout << "CMyFrameWnd Destructor \n"; }

};


MY.CPP

#include "my.h"

CMyWinApp theApp; // global object


void main()

{

CWinApp* pApp = AfxGetApp();

}

執行結果是:

CObject Constructor
CCmdTarget Constructor
CWinThread Constructor
CWinApp Constructor
CMyWinApp Constructor


CMyWinApp Destructor
CWinApp Destructor
CWinThread Destructor
CCmdTarget Destructor
CObject Destructor

好,你看到了,Frame1 並沒有new 任何對象,反倒是有一個全域對象theApp 存在。C++ 規定,全域對象的構造將比程序進入點(在DOS 環境爲main,在Windows 環境爲
WinMain)更早。所以theApp 的構造式將更早於main。換句話說你所看到的執行結果中的那些構造式輸出動作全都是在main 函數之前完成的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章