MFC Windows应用程序设计
文章目录
第一章 Windows程序基础
1.1基本介绍
1.1.1基本概念
在Windows操作系统上运行的应用程序叫做Windows应用程序。
绝大多数Windows应用程序都具有图形界面并由事件来驱动运行
特点
- 具有图形化界面
- 程序由事件驱动
基本结构
1.主函数
2.消息处理函数
二者都是操作系统调用
PS:由系统调用的函数叫做回调函数
主函数主要任务
1.创建图形窗口界面
2.进入消息循环,等待用户事件产生,接收后,把事件信息传送给系统


Windows操作系统与Windows程序的主函数名
控制台程序入口:main
Windows应用程序入口:Winmain


DOS:磁盘操作系统
早期Windows在DOS上运行,单线程,固定执行顺序,入口函数为main
后来,在此基础上开发出Windows,有多任务并发运行控制等功能,为避免冲突入口函数命名为Winmain
1.1.2 Windows内核、API和开发工具
1.内核与API
内核:操作系统的核心代码
API:应用程序接口,系统开放给用户应·用程序可调用的函数。API函数也叫做系统调用,它既不是用户函数,也不是编译器提供的库函数,而是由操作系统提供的函数。
Windows API函数类型:
窗口管理函数
图形设备函数:图形设备接口(GDI)
系统服务函数
2. Windows程序开发工具
早期:SDK,直接使用API函数开发,需要记忆和掌握大量API函数
目前:面向对象程序设计思想和方法出现后,用类对Windows API函数进行封装。比如微软基础类库(MFC) 。
1.2 Windows的数据类型
- Windows对C基本数据类型定义了很多别名,这些别名关键字都为大写。提高应用程序可读性。
- 定义了大量结构类型。
在Windows程序设计中可以使用C基本数据类型也可使用Windows自定义的数据类型。
unsigned:无符号的


1.2.1 Windows的一个特殊类型—句柄
1.内核对象及其句柄 P10 表1-1
例HINSTANCE hlnstance
“HINSTANCE”表示句柄类型
"hlnstance ”句柄类型变量,用来表示一个内核对象–描述事物的数据结构实例。可以是窗口、按钮、文本框、滚动条…
实质:句柄是结构类型变量指针的再封装(4字节整数),在使用上和指针也相似,但不能像指针那样参与运算。

2. HINSTANCE句柄
应用程序实例句柄,也是代表一个内核对象,一个正在运行着的程序。操作系统通过该句柄控制程序运行。
通常给操作系统使用


1.2.2窗口类 WNDCLASS
Windows应用程序重要的内核对象:窗口,程序启动后首要任务委托系统为自己创建窗口。
WNDCLASS,是一个结构体,窗口定制清单。


重要内定制部分



1.2.3 Windows函数的调用说明
两种常用调用约定:
__stdcall
_cdecl(VC++默认)
WIN32 API遵循_stdcall调用约定,所以必须含WIN32 API函数前显示加上___stdcall
系统同样定义了很多别名(__stdcall的名字有很多种命名方式)

1.3窗口的创建和显示
1.使用WNDCLASS结构变量定制符合需要的窗口
2.将定制的窗口向系统注册
BOOL RegisterClass (WNDCLASS &wc)
3.调用创建窗口的API函数在内存中创建窗口
CreateWindow () ;
4.调用API函数将窗口显示到显示器屏幕
ShowWindow() ;
UpdateWindow() ;
思路:定制、注册、创建、调用
1.4 事件、消息循环和窗口函数
1.事件与消息 P19 表1-2
事件:用户提出的服务要求,或者说是需要程序产生动作的原因或刺激。
消息:对事件的描述。
Windows定义的表示消息的结构:

2.消息队列和消息循环
为了不丢失消息,设置一个消息队列存储空间存储消息。GetMessage (函数从消息队列获取消息。为了能够不断从从消息队列获取消息,程序要组织一个叫“消息循环”的循环体。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9GDHXkt-1672836917040)(VC++.assets/image-20220829114325954.png)]
1.5 Windows应用程序的结构
C源文件部分:
主函数:创建窗口并显示窗口,建立消息循环
窗口函数:处理消息,是完成应用程序任务的核心,需要程序员编写大量代码的地方


窗口函数

独立于WinMain,是一个Switch-casa结构
对窗口函数声明的约束:
1.函数名与窗口结构中指针lpfnWndPro的名称相同
2.参数和返回类型与系统要求相符

1.6 Windows程序代码重构
**代码重构:**对代码进行整理使之符合“高内聚、低耦合”的软件设计原则, 使之便于维护和使用。


WM_PAINT:重绘消息,调用函数,如上为一个封装好的函数OnPaint
InvalidateRect()函数:发送WM_PAINT消息
TextOut:向窗口输出文字
1.7 完整实例
该实例未进行封装
#include <Windows.h>
//typedef const char* LPCSTR;
//typedef const wchar* LPCWSTR;
//声明窗口函数原型
//LRESULT long int 长整形
//Win32平台运行
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//主函数
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreInstance, _In_ LPSTR CmdLine, _In_ int nCmdShow)//前面加_In_或_In_opt_,解决报错
//HWND 窗口句柄
{
HWND hwnd1;
HWND hwnd2;
HWND hwnd3;
MSG msg;//消息 //存储消息的变量
//定义注册窗口1
char lpszClassName1[] = "窗口1";
WNDCLASS wc1;//窗体结构体定义 //窗口类变量
wc1.style = 0;
wc1.lpfnWndProc = WndProc;处理消息函数,可修改,但需要保持一致
wc1.cbWndExtra = 0;
wc1.cbClsExtra = 0;
wc1.hInstance = hInstance;//WinMain中传参
wc1.hIcon = LoadIcon(NULL, IDI_APPLICATION);//IDI_APPLICATION,标识符
wc1.hCursor = LoadCursorW(NULL, LPCWSTR(IDC_ARROW));//报错,需要强转 ,加载箭头,鼠标的箭头
wc1.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//窗口颜色,设置为白色,是通过画笔实现,GetStockObject(WHITE_BRUSH)函数获取内置颜色画笔
wc1.lpszMenuName = NULL;//菜单
wc1.lpszClassName = lpszClassName1;//报错,需要强转
RegisterClass(&wc1);
//定义窗口2
WNDCLASS wc2;//窗体结构体定义
char lpszClassName2[] = "窗口2";
wc2.style = 0;
wc2.lpfnWndProc = WndProc;//处理消息函数,可修改,但需要保持一致
wc2.cbWndExtra = 0;
wc2.cbClsExtra = 0;
wc2.hInstance = hInstance;//WinMain中传参
wc2.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc2.hCursor = LoadCursorW(NULL, LPCWSTR(IDC_ARROW));
wc2.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);//窗口颜色,设置为灰色
wc2.lpszMenuName = NULL;
wc2.lpszClassName = lpszClassName2;//报错,需要强转
RegisterClass(&wc2);
hwnd1 = CreateWindow(lpszClassName1,//创建Win1窗口,但存在内存中
"Windows1",//名称
WS_OVERLAPPEDWINDOW,//可叠放
120, 50, 700, 500,//窗口位置,大小
NULL,
NULL,
hInstance,
NULL
);
hwnd2 = CreateWindow(lpszClassName2,//创建Win2窗口,报错就这样写:LPCUWSTR(lpszClassName)
"Windows2",
WS_OVERLAPPEDWINDOW,
150, 80, 750, 550,
NULL,
NULL,
hInstance,//hInstance 窗口实例
NULL
);
hwnd3 = CreateWindow(lpszClassName1,//创建Win3窗口
"Windows3",
WS_OVERLAPPEDWINDOW,
120, 130, 500, 300,
NULL,
NULL,
hInstance,
NULL
);
//显示、更新Win1、2、3
ShowWindow(hwnd1, nCmdShow);
UpdateWindow(hwnd1);
ShowWindow(hwnd2, nCmdShow);
UpdateWindow(hwnd2);
ShowWindow(hwnd3, nCmdShow);
UpdateWindow(hwnd3);
while (GetMessage(&msg, NULL, 0, 0)) // 消息循环
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//处理消息的窗口函数
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message)//消息
{
case WM_LBUTTONDOWN://鼠标左键点击消息
{
MessageBeep(0);//用户的消息处理函数,可发出声音,点击后
}
break;
case WM_DESTROY:
PostQuitMessage(0);//关闭程序函数
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);//系统的消息处理函数
}
return 0;
}
1.8(补)HDC与HWND的区别
常用句柄 :
HBITMAP 保存位图信息的内存域的句柄
HBRUSH 画刷句柄
HCTR 子窗口控件句柄
HCURSOR 鼠标光标句柄
HDC 设备描述表句柄
HDLG 对话框句柄
HFONT 字体句柄
HICON 图标句柄
HINSTANCE 应用程序实例句柄
HMENU 菜单句柄
HMODULE 模块句柄
HPALETTE 颜色调色板句柄
HPEN 笔的句柄
HWND 窗口句柄
hWnd(Handle of Window)
- h: 是类型描述,表示句柄;
- wnd: 是变量对象描述,表示窗口
- 窗口句柄: 其中包含窗口的属性。
- 例如: 窗口的大小、显示位置、父窗口。
hDC(Handle to Device Context)
- 是图像的设备描述表,窗口显示上下文句柄,其中可以进行图形显示。
利用hDC=GetDC(hWnd),可以获得一个窗口的图形设备描述表。可以通过**ReleaseDC()**函数释放。
hWnd句柄是描述一个窗口的形状、位置、大小、是否显示、它的父窗口、兄弟窗口、等等的一组数据结构;
hDC句柄是一个实实在在的用于具体表现这个窗口时,需要对这个窗口有个场合来实现的地方。
hWnd是窗体句柄;hDC是设备场景句柄。
hWnd与窗口管理有关**;hDC**与绘图API(GDI函数)有关。
hWnd是windows给窗口发送消息(事件)用的;hDC是把窗口绘制在屏幕上用的。
有了hWnd,可以使用API的GetDC()函数得到与其相关的hDC:hDC=GetDC(hWnd)。
前者是HWND是窗口句柄,HDC是设备描述表的句柄。
那么再次讲述一下句柄:
句柄实际上是一种指向某种资源的指针,但与指针又有所不同:HWND是跨进程可见的,而指针从来都是属于某个特定进程的。指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针,但不要直接操作它),平时我们只是在调用API函数时利用这个句柄来说明要操作哪段内存。
因为设备描述表中记录和某设备相关的各种信息,比如对于显示器来说,记录了显示器的尺寸、分辨率,还有当前选择的画笔、画刷、字体等GDI对象的信息。可以将HDC理解做一个设备的表面,比如显示器的声明、打印机的表面等等,我们可以使用这个HDC在这些表面上绘制图形——很多GDI绘图函数,都需要使用这个HDC作为参数的。
两个小栗子:
HWND hwnd;//窗口句柄
char szAppName[] = "window1";
//创建窗口
hwnd = CreateWindow(szAppName, //窗口类型名
TEXT("The First Experiment"), //窗口实例的标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //窗口左上角位置坐标值x
CW_USEDEFAULT, //窗口左上角位置坐标值y
800, //窗口的宽度
600, //窗口的高度
NULL, //父窗口的句柄
NULL, //主菜单的句柄
hInstance, //应用程序实例句柄
NULL );
//显示窗口
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
HWND hwnd;//窗口句柄
char szAppName[] = "window1";
//创建窗口
hwnd = CreateWindow(szAppName, //窗口类型名
TEXT("The First Experiment"), //窗口实例的标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //窗口左上角位置坐标值x
CW_USEDEFAULT, //窗口左上角位置坐标值y
800, //窗口的宽度
600, //窗口的高度
NULL, //父窗口的句柄
NULL, //主菜单的句柄
hInstance, //应用程序实例句柄
NULL );
//显示窗口
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
1.9 Windows程序代码封装
消息知识点补充:
WM_PANIT消息
向窗口用户区输出图形必须在WM_PANIT消息对应的程序段中执行。
产生WM_PANIT消息
1.窗口在屏幕上刚显示的瞬间
2.窗口发生更新消息
消息映射表
窗口函数,通过消息标识来查找消息处理代码。可以用查表程序代替,进一步可以使用函数指针指向消息处理函数。


设计思路:
为了实现功能的封装,将switch结构换成for循环的思路,将消息名与消息处理函数封装成结构体,多个消息结构体构成结构体数组,通过匹配封装结构体来定位函数并调用。
观察者模式
鉴于Windows这种把消息发送者和消息处理者分开的做法的优点,本方法后来演化成了现在常用的一种设计模式—观察者模式。

在以后的年代里,Java利用观察者模式实现了事件监听器模型,而C#则利用观察者模式演化出了独特的事件委托模型。
第二章 Windows程序的类封装
C++类封装
主函数的任务是创建并显示窗体和实现消息循环。如果用面向对象的思想来考虑的话﹐主函数的函数体可以看成是一个对象—应用程序类对象,而其中的窗体应该是嵌人在这个应用程序类对象中的另一个对象—-窗口类对象﹐而消息循环应该是应用程序类对象的一部分。故此,为了形成程序框架﹐应该声明两个类:应用程序类和窗口类。
窗口类的声明
窗口类型的注册、窗口的创建和显示等与窗口相关的功能。同时在类中还应该有一个HWND类型的窗口句柄hWnd,作为类的数据成员。因此,窗口类只要把窗口句柄及对窗口操作的API 函数封装到一起就可以了。如果把这个类命名为CFrameWnd,于是窗口类CFrameWnd 的声明为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-535KGCNx-1672836917045)(VC++.assets/image-20220905213549269.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3FrUNGr-1672836917045)(VC++.assets/image-20220905213619345.png)]
代码中函数UpdateWindow( )和ShowWindow()前面的符号“::”是域作用符.如果在符号的前面是空白则表明其后的函数是系统函数。因为这里的两个函数是 Windows的两个API函数,UpdateWindow( )和 ShowWindow( )不属于任何类,所以在这里用域作用符说明它们是系统函数。
应用函数类的声明
如果把主函数中的整个函数体作为一个对象﹐并把它叫做应用程序﹐则还应该声明一个应用程序类,并命名为CWinApp。
窗口类的对象:m_pMainWnd作为类的数据成员
**InitInstance( )**通过调用窗口类的成员函数来完成窗口对象m_pMain Wnd 的注册.创建﹑显示等工.作。
**Run( )**则用来完成消息循环的工作。


以上两部分类主要是封装了创建一个窗口程序基本的内容,如想调用以上代码,只需要创建一个对象
主程序封装
在上面这两个类的支持下,Windows程序的设计就相当简单了:先定义一个CWinApp类的全局对象theApp,然后在主函数中按照顺序逐个地调用对象theApp的成员函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YRDHqZkT-1672836917047)(VC++.assets/image-20220905220627226.png)]
C++基础——类与对象
作为一门语言其相关基础相差不大,本部分主要介绍其独有的知识点以及注意事项
类的定义以及实现
//三种权限
//公共权限 public 类内可以访问 类外可以访问
//保护权限 protected 类内可以访问 类外不可以访问
//私有权限 private 类内可以访问 类外不可以访问
//class结束后需要加分号
class Person
{
//姓名 公共权限
public:
string m_Name;
//汽车 保护权限
protected:
string m_Car;
//银行卡密码 私有权限
private:
int m_Password;
public:
void func() //类内可以访问保护和私有权限,一种函数的实现方法
{
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123456;
}
};
void Person::func(){//第二种函数的实现方式,双冒号前为函数的所有类,若为公共则为空
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123456;
}
int main() {
Person p;
p.func(); //可通过访问公有权限中的函数间接访问私有权限
p.m_Name = "李四";
//p.m_Car = "奔驰"; //保护权限类外访问不到
//p.m_Password = 123; //私有权限类外访问不到
system("pause");
return 0;
}
class与struct的区别
在不加修饰符的条件下,class默认私有,struct默认公共
构造函数和析构函数
构造函数: 用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数: 主要作用在于对象 销毁前 系统自动调用,执行一些清理工作
c++中构造函数和析构函数会被编译器自动调用,完成对象初始化和清理工作
如果不提供构造函数和析构函数,编译器会提供
编译器提供的构造函数和析构函数是空实现
构造函数语法:类名(){}
构造函数,没有返回值也不写void
函数名称与类名相同
构造函数可以有参数,因此可以发生重载
程序在调用对象时候会自动调用构造,无须手动调用,且只会调用一次
析构函数语法: ~类名(){}
析构函数,没有返回值也不写void
函数名称与类名相同,在名称前加上符号 ~
析构函数不可以有参数,因此不可以发生重载
程序在对象销毁前会自动调用析构,无须手动调用,且只会调用一次
构造函数的分类及调用(匿名函数)
匿名函数:
Person(10)单独写就是匿名对象 当前行结束之后,系统马上回收匿名函数
两种分类方式:
按参数分为: 有参构造和无参构造
按类型分为: 普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造
//如果报错无默认类,就在类里加上一个类名()的默认就行,多个构造函数重载的,但默认的构造函数最好加上
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//调用拷贝函数: Person p6(p1);
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
}
//调用有参的构造函数
void test02() {
//2.1 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2();
//2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象 当前行结束之后,系统马上回收匿名函数
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person p5(p4);
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
const修饰成员函数
常函数:
void ShowPerson() const{}
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
const Person person;
声明对象前加const称该对象为常对象
常对象只能调用常函数
示例:
class Person {
public:
Person() {
m_A = 0;
m_B = 0;
}
//this指针的本质是一个指针常量,指针的指向不可修改
//如果想让指针指向的值也不可以修改,需要声明常函数
void ShowPerson() const {
//const Type* const pointer;
//this = NULL; //不能修改指针的指向 Person* const this;
//this->mA = 100; //但是this指针指向的对象的数据是可以修改的
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->m_B = 100;
}
void MyFunc() const {
//mA = 10000;
}
public:
int m_A;
mutable int m_B; //mutable 可修改 可变的
};
//const修饰对象 常对象
void test01() {
const Person person; //常量对象
cout << person.m_A << endl;
//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
person.MyFunc(); //常对象不能调用const的函数
}
int main() {
test01();
system("pause");
return 0;
}
继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 :public Base1
{
public:
void func()
{
m_A; //可访问 public权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass()
{
Son1 s1;
s1.m_A; //其他类只能访问到公共权限
}
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2:protected Base2
{
public:
void func()
{
m_A; //可访问 protected权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass2()
{
Son2 s;
//s.m_A; //不可访问
}
//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //可访问 private权限
m_B; //可访问 private权限
//m_C; //不可访问
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
//m_A;
//m_B;
//m_C;
}
};
多态
多态分为两类
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
多态满足条件
有继承关系
子类重写父类中的虚函数
①虚函数就是创建了虚函数表指针,表内记录的虚函数的地址)
②子类重写虚函数时,会将父类虚函数表替换为子类新的虚函数

多态使用条件
父类指针或引用指向子类对象
多态的优势:
组织结构清晰,出错易于定位
可读性高,代码易于理解
便于扩展和维护
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak() //重写父类虚函数
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal & animal) //父类引用指向子类对象
{
animal.speak();
}
//或直接在堆区创建一个新的对象Animal *animal = new Cat;
//animal->func();
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
this
this指针:this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址。this只能在成员函数中使用。全局函数,静态函数都不能使用this。
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
示例:
class Base
{
public:
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func调用" << endl;
};
};
void test01()
{
Base * base = NULL;
//base = new Base; // 错误,抽象类无法实例化对象
base = new Son;
base->func();
delete base;//记得销毁
}
int main() {
test01();
system("pause");
return 0;
}

MFC窗体初始化封装类完整实例
解决字符以及适配问题1:
初始条件:解决方案平台:x86(win32)
字符集:无或多字符集;
属性-》c/c+±》语言-》符合模式:否
所有平台统一win32
解决字符以及适配问题1:
字符兼容问题:

或char全部换成WCHAR,且所有字符串前加L:如:p=L”你好”
完整代码:
// 03.cpp : 定义应用程序的入口点。
//
#include<Windows.h>
//定义全局变量和函数
HINSTANCE hInst;
HINSTANCE hInstance;
MSG msg;
char lpszClassName[] = "windows_class";
//char ShowText[]= "Hello_YunMo!";
char* ShowText;
//声明函数原型
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//16位的变量就被命名为wParam, 32位的变量就被命名为lParam。wParam和lParam这两个是Win16系统遗留下来的产物,在Win16API中WndProc有两个参数:一个是WORD类型的16位整型变量;另一个是LONG类型的32位整型变量。
void OnLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); void OnButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnDestroy(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
//窗口类
class CFrameWnd
{
public:
HWND hWnd;
public:
int RegisterWindow();
void Create(LPCTSTR lpClassName,
LPCTSTR lpWindowName);
void ShowWindow(int nCmdShow);
void UpdateWindow();
};
//LPCSTR A 32-bit pointer to a constant character string.
//常量指针,一般用于参数传递和固定字符串
//LPSTR A 32 - bit pointer to a character string.
//普通指针,一般用于字符串操作
//窗口类成员
int CFrameWnd::RegisterWindow() {
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;//HINSTANCE 是“句柄型”数据类型。相当于装入到了内存的资源的ID。
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//窗口颜色,设置为白色,是通过画笔实现,GetStockObject(WHITE_BRUSH)函数获取内置颜色画笔
wc.lpszMenuName = NULL;//菜单
wc.lpszClassName = lpszClassName;
return RegisterClass(&wc);
}
void CFrameWnd::Create(LPCTSTR lpClassName, LPCTSTR lpWindowName) {
RegisterWindow();
hInst = hInstance;
hWnd = CreateWindow(lpszClassName,
lpWindowName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
NULL,
NULL,
hInstance,
NULL);
}
void CFrameWnd::ShowWindow(int nCmdShow)
{
::ShowWindow(hWnd, nCmdShow);
}
void CFrameWnd::UpdateWindow()
{
::UpdateWindow(hWnd);
}
//应用程序类
class CWinApp
{
public:
CWinApp* m_pCurrentWinApp;
public:
CWinApp();
~CWinApp();
public:
CFrameWnd* m_pMainWnd;
public:
virtual BOOL InitInstance(int nCmdShow);
int Run();
};
CWinApp::CWinApp()
{
m_pCurrentWinApp = this;
}
BOOL CWinApp::InitInstance(int nCmdShow) {
m_pMainWnd = new CFrameWnd;
m_pMainWnd->Create(NULL, "封装的Windows程序");
m_pMainWnd->ShowWindow(nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
int CWinApp::Run() {
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);//也就是说TranslateMessage会发现消息里是否有字符键的消息,如果有字符键的消息,就会产生WM_CHAR消息,如果没有就会产生什么消息。VUE的VUEX用到了类似的函数名
DispatchMessage(&msg);//返回值是指定窗口过程的返回值。虽然这意味着返回值依赖于消息的分派,但通常该返回值会被忽略。
}
return msg.wParam;
}
CWinApp::~CWinApp() { delete m_pMainWnd; }
//程序员派生的窗口类
class CMyWnd :public CFrameWnd {
};
//由程序员自CWinApp类派生的CMyApp类
class CMyApp :public CWinApp {
public:
BOOL InitInstance(int nCmdShiow);
};
//派生类CMyApp的成员函数
BOOL CMyApp::InitInstance(int nCmdShow)
{
CMyWnd* pMainWnd;
pMainWnd = new CMyWnd;//应用窗体的派生类定义窗体对象
pMainWnd->Create(NULL, "应用窗体的派生类的程序");
pMainWnd->ShowWindow(nCmdShow);
pMainWnd->UpdateWindow();
m_pMainWnd = pMainWnd;//吧CMyWnd类的对此昂赋给m_pMainWnd
return TRUE;
}
//程序员定义的CWinApp的对象MyApp
CMyApp MyApp;
//全局函数AfxGetApp(有点不太理解。AfxGetApp( )是全局的。
//AfxGetApp()这个函数可以得到当前应用进程的指针,是CWinApp* 类型的,通过这个指针可以访问到这个进程中的对象。)
CWinApp* AfxGetApp() {
return MyApp.m_pCurrentWinApp;
}
//主函数
int APIENTRY WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreInstance, _In_ LPSTR CmdLine, _In_ int nCmdShow) {
int ResultCode = -1;
CWinApp* pApp;
pApp = AfxGetApp();//获取m_pCurrentWinApp的值
pApp->InitInstance(nCmdShow);
return ResultCode = pApp->Run();
}
//窗口函数的实现
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message)//消息
{
case WM_LBUTTONDOWN://鼠标左键点击消息
{
OnLButtonDown(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT://鼠标左键点击消息
{
OnPaint(hWnd, message, wParam, lParam);
}
break;
case WM_DESTROY:
PostQuitMessage(0);//关闭程序函数
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);//系统的消息处理函数
}
return 0;
}
//消息函数实现
void OnLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
ShowText = "Hello YunMo!";//会报错不匹配,如下方式则不会但是会乱码,方法是:属性->C++->语言->符合模式->否
//char* str = "Hello_YunMo!";
//ShowText = str;
InvalidateRect(hWnd, NULL, 1);
}
void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 50, 50, ShowText, 12);//属性12指的是显示字符个数,用showtext乱码
EndPaint(hWnd, &ps);
}
void OnDestroy(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
PostQuitMessage(0);
}
该部分借鉴博客C++:类与对象_做一只猫的博客-CSDN博客_c++类与对象 个人觉得写的非常全面且易懂
窗口类的处理与封装
在窗口类中声明成员函数AfxWndProc (,把窗口函数(WndProc () )的所有代码移到该函数中。然后在窗口函数( WndProc( )中调用AfxWndProc () 。



消息映射
1.类的消息注册表
//由CFrameWnd派生的CMyWnd类
class CMyWnd :public CFrameWnd
{
private:
char* ShowText;//声明·一个字符串为数据成员
public:
afx_msg void OnPaint();//声明WM_PAINT消息处理函数
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);//鼠标左键按下消息处理函数,注意这里传参问题,需要根据需求传参
DECLARE_MESSAGE_MAP()//声明消息映射
};
消息映射表

整体以结构体数组的形式存储
类B接受消息后调用消息处理函数的过程

消息映射表的声明和实现
在设计一个需要响应消息的类时,必须在类中声明一个消息映射表,并实现(填写表内容)。
//消息映射实现
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
MFC消息类型:三大类
标准消息、命令消息和“Notification消息”
标准消息:标识符WM_××××
宏ON_WM_××××
对应消息处理函数On×x×(系统默认)
例:ON_WM_LBUTTONDOWN()afx_.msg void OnLButtonDown();
命令消息oN_COMMAND(<消息标识>,<对应消息处理函数>)ON_COMMAND (IDM_FILENEW,OnEileNew)
Notification消息——暂不介绍
完整实例
winmain已经被封装在其中,不用单独调用
// 04_2-5.cpp : 定义应用程序的入口点。
//
#define _AFXDLL
#include <SDKDDKVer.h>
#include<afxwin.h>
#include "framework.h"
#include "04_2-5.h"
//由CFrameWnd派生的CMyWnd类
class CMyWnd :public CFrameWnd
{
private:
char* ShowText;//声明·一个字符串为数据成员
public:
afx_msg void OnPaint();//声明WM_PAINT消息处理函数
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);//鼠标左键按下消息处理函数,注意这里传参问题,需要根据需求传参
DECLARE_MESSAGE_MAP()//声明消息映射
};
//消息映射实现
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
//WM_PAINT消息处理函数的实现
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
dc.TextOut(20, 20, ShowText);
}
//WM_LBUTTONDOWMT消息处理函数的实现
void CMyWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
ShowText = "有消息映射表的程序";
InvalidateRect(NULL, TRUE);//通知更新
}
//程序员由CWinApp派生的应用程序类
class CMyApp :public CWinApp {
public :
BOOL InitInstance();
};
BOOL CMyApp::InitInstance()
{
CMyWnd* pMainWnd = new CMyWnd;
pMainWnd->Create(0, "MFC");
pMainWnd->ShowWindow(m_nCmdShow);
pMainWnd->UpdateWindow();
m_pMainWnd = pMainWnd;
return TRUE;
}
//定义CMyApp的对象MyApp
CMyApp MyApp;
第三章MFC立用程序框架

3.1早期应用程序框架及其MFC类
3.1.1早期的应用程序框架
早期应用程序框架由两个对象组成:应用程序类CwinApp的派生类对象和窗口类CErameWnd的派生类对象,后者作为一个成员对象嵌在前者之中。在应用程序主函数WinMain () 中,cWinApp派生类的对象theApp通过调用自己的成员函数来完成程序的初始化及消息循环等一系列工作。
3.1.2 MFC的窗口类
窗口类CErameWnd的继承关系(从上往下)
1.CObeject类
2.CCmdTarget类
3.Cwnd类
4.CErameWnd类
3.1.2CWinApp类
MFC把程序的主函数体也作为对象来处埋- CWinApp
CWinThread:封装了一些用于线程管理的功能函数。
注意:窗口类成员对象封装在CWinThread中。

CwinApp类可重写虚函数成员
程序设计时,通过CWinApp派生自己的应用程序类,并对函数Initlnstance进行重写,实现对窗口的不同要求.
virtual BOOL InitInstance ( );
virtual int ExitLnstance(); //return app exit code
virtual int Run () ;
3.3应用程序的文档/视图结构
目前,用MFC设计的Windows应用程序几乎都采用文档/视图结构。
和简单框架相比:
主窗口对象被拆分成: CErameWnd对象、视图类CView对象、和文档类CDocument对象三个对象。
这种程序框架较复杂,一般由MFC AppWizard自动生成。
3.3.1文档/视图结构的基本概念
早期窗口类负责处理的任务繁重,MFC把早期窗口类的功能分解成三个部分:
1.数据存储、管理部分—文档类CDocument类
2.数据显示与交互部分—视图类CView类
3.窗口框管理部分(大小、标题、菜单条等)—-—-窗口框架类CFrameWnd类

3.3.2单文档界面和多文档界面
两种类型的文档/视图结构程序
1.单文档界面
2.多文档界面

3.4文档类CDocument的派生类
class CMyDoc : public CDocument
{
protected: // create from serialization only
CMyDoc();
DECLARE_DYNCREATE(CMyDoc)
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyDoc)
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CMyDoc();
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CMyDoc)
afx_msg void OnFileNew();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
3.5视图类CView的派生类
class CMyView : public CView
{
protected: // create from serialization only
CMyView();
DECLARE_DYNCREATE(CMyView)
public:
CMyDoc* GetDocument();
public:
//{{AFX_VIRTUAL(CMyView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL
public:
virtual ~CMyView();
protected:
//{{AFX_MSG(CMyView)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
视图类重要的成员函数:
GetDocument()函数
获得文档类对象指针,联系视图类对象和文档类对象。
Ondraw(CDC* pDC)函数 (重画)
pDC一般为注释状态,需要使用到时取消注释
一个消息处理函数,更新视图的显示。
3.6 窗口框架类CFrameWnd的派生类
class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)
public:
virtual ~CMainFrame();
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
// Generated message map functions
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
3.7 文档模板类

目前理解就是通过MFC向导创建一个完整的项目,对项目本身进行修改,基本类都已经创建完成,剩下的就是如何继承以及内容的添加。
程序员主要工作
1.重写CWinApp派生类的虚函数!n.itLnstance
2.在CDocument的派生类中,声明程序所需数据和数据操作的接口函数
3.在CView类的派生类中编写消息处理代码。若需要用到文档类对象中数据,通过该类成员函数GetDocument()来获得文档对象,通过接口函数对文档中的数据进行操作
3.在CView类的派生类的OnDraw中编写窗口重绘时的代码。
4.用宏实现类的消息映射表
3.9 MFC文档/视图应用程序框架中各个对象的关系P81
3.9.1应用程序各个对象创建的顺序
1.应用程序对象是全局对象,它在系统启动之前由
系统创建。
2.程序主函数首先调用应用程序对象的初始化函数Initlnstance(),并在该函数中创建文档模板对象。3.利用文档模板类对象构造函数创建文档、视图、窗口框架3个对象。其中,视图对象是由框架窗口创建并管理的。
4.应用程序创建文档模板对象并将其加入到应用程序对象维护的文档模板链表。

3.9.2 应用程序各对象之间的联系
1.以文档为中心的结构
应用程序对象维护一个文档模板链表对多个文档模板进行管理。
文档模板委托CDocManager类对象管理文档对象和窗口框架。文档对象有一个指针指向所属文档模板,文档对象还维护一个链表,链表每个节点都指向与文档相关联的视图对象。
框架窗口对象是视图对象的容器,维护一个由它管理的视图对象链表,还有一个指针指向当前活动视图。

2.应用程序框架对象之间的联系方法
MFC应用程序框架的各个对象都从各自基类继承了一些获得其它对象指针的方法,从而可以通过这些指针与其它对象的成员来互相联系。
例如∶
在视图类中,可以使用GetDocument (获得与其关联的文档对象指针。
任何对象都可使用全局函数AfxGetApp获得应用程序对象。