深入浅出MFC-学习笔记
VC基础巩固学习-MFC
一些名词
- API:Application Programming Interface
- SDk: SoftWare Development Kit
- MFC: Microsoft Foundation Classes
- OWL: Object Windows Library
- …
第一章 Win32基本程序概念
Windows支持动态链接。 Windows程序调用的函数可分为C Runtimes以及Windows API两大部分。
LIBC.LIB 这是C Runtime函数库的静态链接版本。
MSVCRT.LIB 这是C Runtime函数库到动态链接版本。(程序执行时必须有MSVCRT40.DLL在场)
Windows程序设计最重要的观念:模块将各类消息分类到所属的队列中去,然后以这些消息为基础的事件驱动系统。
CreateWindow()只产生窗口,并不显示窗口。利用ShowWindow()将它显示在屏幕上。
- 窗口只需注册一次,“只有第一个实例才会进入”的InitApplication函数中,所以RegisterClass操作写在这个函数里面。
- 产生窗口,每个实例都需要进入InitInstance函数中。所以把CreateWindow()写在这个函数里面。
- 程序的进入,以及窗口的注册和产生
- 程序通过WinMain()进入,通过InitApplication()函数注册窗口,在InitApplication()里封装类窗口类,使用RegisterClas()来初始化注册窗口信息,通过InitInstance()函数为实例创建窗口,使用CreateWindow()来为实例创建窗口,并且初始化窗口信息。使用ShowWindow()来将窗口显示出来。之后进入消息循环状态,通过GetMessage()非强制性多任务的函数,用DispatchMessage()将消息分派给所属的窗口函数去处理。TranslateMessage()用来转换键盘消息。
int CALLBACK winMain(HINSTANCE hInstance, hInstance hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
UNREFERENCED_PARAMETER(lpCmdLine);
if(!hPrevInstance)
if(!InitApplication(hInstance)) //窗口注册
return (FALSE);
if(!InitInstance(hInstance, nCmdShow)) //产生窗口
return (FALSE);
while(GetMessage(&msg, NULL, 0, 0)) //消息循环
{
TranslateMessage(&msg); //转换键盘消息
DIspatchMessage(&msg); //分派消息
}
return (msg.wParam);
}
- 窗口函数通过switch/case指令来分派各类消息。注意的是:无论什么消息都必须被处理,所以default处,调用DefWindowProc,这是Windows内部默认的消息处理函数。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message){
case WM_COMMAND;
...
break;
case WM_DESTORY:
...
default:
return (DefwindowProc(hWnd, message, wParam, lParam));
}
return (0);
}
定义成CALLBACK回调函数,是为了当某个消息或时间发生,开放接口给操作系统调用。
- 封装了消息处理,将“数据”和“处理数理的方法封装”,消息映射的雏形
struct MSGMAP_ENTRY
{
UINT nMessage;
LONG (*pfn)(HWND, UINT, WPARAM, LPARAM);
};
#define dim(x) (sizeof(x) / sizeof(x[0]));
//消息与处理例程的对照表格
struct MSGMAP_ENTRY _messageEntries[] =
{
WM_CREATE , OnCreate ,
WM_PAINT , OnPaint ,
WM_COMMAND , OnCommand,
.....
WM_DESTORY , OnDestory,
};
//Command-ID 与处理例程之对照表格
struct MSGMAP_ENTRY _commandEntries[] =
{
IDM_ABOUT , OnAbout ,
IDM_FILEOPEN , OnFileOpen,
IDM_SAVEAS , OnSaveAs ,
};
//窗口函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int i;
for(i = 0; i < dim(_messageEntries); i++)
{
if(message == _messageEntries[i].nMessage)
return ((*_messageEntries[i].pfn)(hWnd, message, wParam, lParam));
}
return (DefwindowProc(hWnd, message, wParam, lParam));
}
//专门处理WM_COMMAND
LONG Command(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int i;
for(i = 0; i < dim(_commandEntries); i++)
{
if(LOWORD(wParam) == _commandEntries[i].nMessage)
return ((*_commandEntries[i].pfn)(hWnd, message, wParam, lParam));
}
return (DefwindowProc(hWnd, message, wParam, lParam));
}
- 对话框的运行
- Windows的对话框依其与父窗口的关系,分为两类:
- “令其父窗口无效,直到对话框结束”。 modal对话框
- “父窗口与对话框共同运行”。 modaless对话框
- 做出一个modal对话框,需要两件东西
- 对话框模板,这是在RC文件中定义的,决定对话框的大小、字形、内部空、各在什么位置等等。
- 对话框函数,形态类似于窗口函数,但是通常只处理WM_INITDIALOG和WM_COMMAND两个消息。每个控件也是一个小窗口,它们有各自的窗口函数。而所有控件传来的消息与其管理者(父窗口,也就是对话框)沟通。
- modal对话框的激活与结束。考的是DialogBox和EndDialog两个API函数。
- 对话框处理过的消息,应该传回TRUE;未处理消息,则应该传回FALSE。因为对话框没有处理,那么系统提供的默认对话框就会接手处理。
- 模块定义文件 (.DEF)
- Windows程序需要一个模块定义文件,将模块名称、程序段和数据段的内存特性、模块堆大小、堆栈带下、所有callbak函数名称等等记下来。
- 在VS集成开发环境中开发程序,不在需要特别准备.DEF文件。
- 资源描述文件 (.RC)
- RC文件是一个以文字描述资源的地方。
- 这些文字描述需经过RC编译器,才产生可使用的二进制代码。
Windows程序的生命周期
- 程序进入WinMain()中,调取InitInstance()函数中的CreateWindow()创建窗口,产生窗口后会送出WM_CREATE消息给窗口函数,后者可以在此时做些初始化操作,之后通过ShowWindow()显示窗口。
- 程序活着的时候,通过GetMessage()抓取消息。如果消息是WM_QUIT,GetMessage()会传回0,结束while循环,进而结束整个程序。
- DispatchMessage通过Windows USER模块的协助与监督,把消息分派至窗口函数。消息被判别并处理。
- 程序不断进行2和3
- 按下系统菜单中的Close命令项时,系统送出WM_CLOSE。通常窗口函数不再拦截此消息,于是DefWindowProc处理它。
- DefWindowProc收到WM_CLOSE后,调用DestroyWindow把窗口清除,DestroyWindow本身又会送出WM_DESTORY消息。
- 程序对WM_DESTORY的标准反应是调用PostQuitMessage.
- PostQuitMessage只会送出WM_CLOSE消息,让GetMessage接收到,执行2结束。
- 空闲时间的处理:OnOdle )
- 所谓的空闲时间就是“系统中没有任何消息等待处理”的时间。
- GetMessage: 会过门不入,于是操作系统再去照顾其他人。
- PeekMessage: 会取回控制权,使程序得以执行一段时间。
传统的SDK程序如果要处理空闲时间,用一下循环代替WinMain中的传统消息循环
while (TRUE)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DIspatchMessage(&msg);
}
else
{
OnIdle();
}
}
- Console程序
- 调用部分的、不牵扯到图形使用者接口(GUI)的Win32 API。这种程序成为console程序。
- 纯“文字窗口”
- Console程序和DOS程序的差别
- 编写方式
- 利用Windows编译器、链接器做出来的程序。调用C Runtime函数和“不牵扯GUI”的Win32 API函数,进入点是main。是console程序
- 过去在DOS环境下开发的程序,成为DOS程序,可以调用C Runtime函数,不能调用Win32 API函数。
- 程序功能
- ConSole功能包含Dos
- 可运行文件格式
- DOS程序是所谓的MZ格式
- Console程序的格式和所有的Win32程序一样,是所谓的PE格式。
第二章 C++ 的重要性质
类及其成员: 谈封装(encapulation)
对象的两大属性:属性property,方法method。
- 把数据声明为private,不允许外部随意存取,只能通过特定的接口来操作,这就是面向对象的封装特性。
基类与派生类: 谈继承(Inheritance)
抽取共有性质,成立基类。再从中衍生出派生类。派生类继承基类的成员。
-
this指针
-
成员函数是有一个隐藏参数,名为this指正。
class CShape
{
private:
int m_color;
public:
void setcolor(int color) { m_color = color };
}
//编译后
class CShape
{
private:
int m_color;
public:
void setcolor(int color, (CShape*)this ) { this->m_color = color };
}
虚函数与多态(Polymorphism)
- 如果以一个“基类之指针”指向“派生类之对象”,那么经由该指针,只能调用基类所定义的函数。
- 如果以一个“派生类之指针”指向一个“基类之对象”,必须先做明显的转型操作。
- 如果基类和派生类都定义了“相同名称的成员函数”,调用哪个函数,由指针的原始类型而定。
虚函数与一般化
如果预期派生类有可能重新定义某一个成员函数,那么你就在基类中把此函数设为virtual。
多态(Polymorphism)
- 以相同的指令却调用了不同的函数,这种性质成为Polymorphism。
- 虚函数是了解多态以及动态绑定的关键。
- 基类的函数什么都不做,就可以将它定义为纯虚函数。拥有纯虚函数的类是抽象类,不能被实例化。
- 纯虚函数不需要定义其实际操作,它的存在只是为了在派生类中被重新定义。
- 如果派生类没有重新定义一个成员函数,那也不能实例化,于是它也是一个抽象类。
- 虚函数派生下去仍为虚函数,而且可以省略virtual关键词。
静态成员 (变量和函数)
当类成员中一个成员变量的值是固定,那么可以在变量前加上static,定义为static成员变量。不要把static成员变量的初始化操作安排的类的构造函数中,因为变量的初值只应该被设定一次。static成员函数同理。
class SavingAcoount
{
private:
char m_name[40];
char m_addr[60];
double m_total;
static doble m_rate;
...
public:
static void setRate(double newRate) { m_rate = newRtae; }
};
doble SavingAcoount::m_rate = 0.0075;
SavingAcoount::setRate(0.0074);
C++程序的生与死: 兼谈构造函数与析构函数
- C++的new运算子符和C的malloc函数都是用于配置内存。new的优点是:new不但配置对象所需的内存空间,同时会引发构造函数的执行。
- static对象的析构函数比全局对象的析构函数要早一步执行。
- 以new方式产生出来的局部对象,析构函数则在对象被delete时执行,否则则会在力离开该对象的活动范围是,其析构函数被执行。