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 函数之前完成的。