第一章 win32 基本程序概念

windows 是一个“以消息为基础的事件驱动系统”。当系统内核捕捉到外围设备发生的事件后,将以一种特定的消息传递出去。而用户程序在接收到相应的消息后再做出相应的处理(否则系统以默认函数处理)。处理窗口过程的一般是窗口函数(window procedure)。Windows 程序的执行流程如上图。
窗口函数习惯上称作回调函数,回调函数类似于C 语言中bsearch (二分法查找)函数的cmp (用于比较两个元素的大小)参数:
// #include <stdlib.h>
void *bsearch(const void *key, const void *base,
              size_t n, size_t size,
              int (*cmp)(const void *keyval, const void *datum)); 
想一想开发bsearch 函数的人怎么会知道两个元素的是什么,怎么比较大小呢?因此就必须留给用户要自己定义cmp 函数了!
回调函数一般都有固定的格式(不知道是否会用变参数的情况),不然可能会发生错误。回调函数一般都是由windows 系统来调用,不是用户自己调用。在用户使用bsearch 函数时,用户自己定义的cmp 函数也是由C 函数库来调用,不是自己调用。
回调函数的概念虽然在C 语言中就已经存在,但使用的范围远没有windows 中的这么广(其实在设计接口时,遇到某些有共性的未知操作就可以用传递一个函数的方法解决--例如遍历某个未知集合中的每个元素)。
回调函数在windows 开发中得到推广应该是由其“以消息为基础的事件驱动系统”本质决定的。用用户要实现某个操作,但是不知道什么时候开始执行(因为不知道什么时候能收到相应的消息);系统则知道什么时候触发操作(因为消息由系统发出),但是又不知道操作的具体细节(操作是用户自己定义的)。在这种时候回调函数就成了用户和系统之间沟通的桥梁--用户自己定义操作的细节,但是由系统在适当的时刻帮助调用。
因此,回调函数就成了“以消息为基础的事件驱动系统”系统平台上程序开发的核心!
为了向面向对象思想看齐,一般把回调函数也设计成类的成员。又因为回调函数有固定格式,不能随便修改,因此在类中要把它声明为static 类型函数(这是利用了C++编译器不会为类中static 函数添加this 指针参数的隐含特征)。
在windows 中程序设计的主要任务就是对自己感兴趣的消息做出相应的处理:程序等待某个特定消息的发生,然后针对该消息做出特定的操作,如此而已!
第二章 C++的重要性质
面向对象有三个核心概念:封装,继承,多态。封装和继承这里不想细说,主要讲一下多态。
很多书里都说多态是面向对象的核心(也不知道对不对)。在C++中支持多态的关键技术就是虚函数。虚函数的有些特征很怪异,这主要是和传统C 函数比较而言的(如果没有传统的函数概念也就不会觉得奇怪了)。
以前很多人用C 语言(现在也有好多人用C 入门),对C 的执行机制很熟悉。C 语言是一种高级的汇编语言,写一段代码就是一段代码,编译器不会暗中给你做什么手脚。即使编译器做也就是在初始化和退出的时候调用一些函数,这些用户都知道(fork、exec、exit……)。
关于函数这一块也一样:函数参数是值传递--数组除外(数组传地址),为了支持变参数,参数是从右到左进栈,函数名就代表一个函数的入口地址,比较复杂一些的就是函数指针。
所以C 程序员在调用一个函数的时候就根本想不出它会有什么出格的行为!
在C++中就不一样了,特别是虚函数,有时候简直搞不清楚它到底是调用了哪个函数(真是麻烦)!
这种情况是由C++是一个面向对象的语言性质决定的。如果你还是用C++编写C 程序,那么它还是一个高级的汇编语言;但是如果你用C++编写(特别是有虚函数的)面向对象程序就不是那么回事了!
例如:
class A { public:
virtual void display() { cout << "class A" << endl; } };
class B: public A { public:
virtual void display() { cout << "class B" << endl; } };
void main()
{
A *pa = new B;
pa->display(); }
执行的结果却打印是:class B
让人感觉不解的地方就是pa 明明是类A 的指针,却是执行了类B 的函数(不可原谅) 其实有这种感觉的人在不知不觉中就犯了一个形而上的错误:用C 语言的函数行为来套用display()的行为。在此我想提醒一点:把C++当作一个新的语言,C 只是参考,不是金科玉律,切记!
很多书上用什么动态绑定来解释虚函数(还保存了一张什么虚函数表),我觉得这可能是因为他们了解一些C++编译器的实现细节。如果他们不知道C++编译器怎么实现的,他们怎么就知道就要用虚函数表来实现虚函数呢(而且用户也不可能知道每个编译器的细节)?
虽然C++编译器是一个黑盒子,但我们仍然可以用C 语言中方法来模拟一个虚函数。我自己喜欢把虚函数看作一个函数指针,该指针初始值为NULL,每遇到函数的定义时就把该指针设置为新定义的函数的地址(当然派生类从基类中继承了这个函数指针)。这样,用户在通过函数指针调用虚函数的行为就很清楚了(如果不熟悉指针就不好办了)。又由于函数是类的成员,不是对象的成员,因此把虚函数看作static 型的函数指针更准确。
第三章 MFC 六大关键技术之仿真
其实除了消息外,其他的几个技术细节都可以看作是面向对象语言的特征。例如:对象的产生过程、动态识别……。动态识别、动态创建、序列化特征已经在JAVA 等新的面向对象语言中得到支持了。如果不想了解编译器的实现细节的话,也可以不看。MFC 本身特有的东西应该是消息的传播机制。当然这里还是要全部总结一下了(毕竟也是这本书最有特色的地方了)。
1 对象创建
MFC 中所有的类都继承自CObject ,创建对象时要考虑其父类的创建。个人觉得是这样一个规则(不知道对不对):创建对象之前要先创建父类,除非它没有父类!构造的函数的调用规则也是这样:如果有就先调用父类的构造函数(这是MFC,不是C++,不考虑多重继承的情况)。
这就像人类的繁衍:一个人要出生,他的爸爸妈妈肯定要先出生,除非他是第一个进化成人类的(或者是人工合成的)。
2 运行时类型识别

我觉得这里的识别有两种级别,打个比方:X 是某个人,还是具有某个人的血统?
这个问题该问谁,怎么问?问X 的爸爸妈妈、爷爷奶奶还是X 自己?如果李四想知道自己
身上是否有李世民的血统,是要亲自问李世民吗(他怎么会知道自己有多少后代)?
正确的办法是:
1.把X 设为李四。
2. X 是不是李世民?
3.如果X 是就停止,并输出结果是。
4.如果X 不是,但X 有爸爸,就X 设成X 的爸爸,然后转到2。
5.如果X 没有爸爸就停止,输出结果否。

在MFC 中保存了一棵类的家族树,CObject 是根结点,其他的类都是他的后代(有几个特殊的除外,如: CPoint 等)。由于类的家族树存放的是类的信息--不是对象的信息,因此只需要保存一个就够了,所以MFC 将这棵树保存为static 类型。
MFC 类的家族树和数据结构中的树并不相同,普通的树通过跟结点就可以访问所有的结点(包括叶子)。但在MFC 中却不行--它只能逆向地从叶子结点向根结点方向访问(从父结点访问不到子结点)。
我自己把这种树叫做逆树(和通常的树相反,好象是反物质一类的东东)。其实在所有关于指针的数据结构中都有这种逆*的存在。你可以想象在一个单向链表中,从一个结点移动到后一个结点时,就回不到之前的结点了(除非你另外保存了它的地址)。在现实中也有很多这种情况:你可以知道你所认识的人,但却很难知道所有认识你的人--这是指针的不可逆性造成的。
MFC 为了隐藏类的家族树的实现细节,定义了2 个宏:DEALARE_DYNAMIC 和IMPLEMENT_DYNAMIC 。DEALARE_DYNAMIC 用于定义变量,IMPLEMENT_DYNAMIC 则进行相应的初始化,宏的具体细节可以参考书中代码。
我比较感兴趣的是AFX_CLASSINIT 的初始化过程,代码如下:
static AFX_CLASSINIT _init_classname(class_name::classclass_name);
struct AFX_CLASSINIT {
AFX_CLASSINIT(CRuntimeClass *pNewClass); };
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass *pNewClass)
{
  pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
  CRuntimeClass::pFirstClass = pNewClass; }
由于_init_classname 是静态的AFX_CLASSINIT 类型,因此在定义的时候自动的调用AFX_CLASSINIT 初始化操作从而将pNewClass 神不知鬼不觉地插入到了CRuntimeClass::pFirstClass 链表的开头(这个pFirstClass 链表在动态识别中还用不着)!
这很有点像一种静态的初始化操作--AFX_CLASSINIT 在运行之前已经被自动地完成了。当然AFX_CLASSINIT 和静态初始化还是有些区别的:我感觉静态初始化应该是在编译时被调用,而不是在执行时被调用。这应该算是C++中一些很晦涩的技巧吧
pFirstClass 链表是通过AFX_CLASSINIT 自动初始化的。但是class_name::classclass_name::m_pBaseClass 指向的同宗链表则完全是手工初始化的(通过宏传递的参数)。在同宗链表中每个类和它的父类都可以用确定名称直接访问(静态的类别型录网中的每个CRuntimeClass 都可以通过一个确定的名称直接访问--不必要从pFirstClass 开始遍历)。
补充一点:定义CObject 类时要手工生成pFirstClass 链表和手工初始化CRuntimeClass。
3 动态创建
MFC 也定义了2 个宏:DEALARE_DYNCREATE 和IMPLEMENT_DYNCREATE。
要动态的生成对象,首先要知道对象的初始化函数,在MFC 中采用在CRuntimeClass 中保存函数指针的方法来实现。保存指针等操作的代码也是在宏中加入的(MFC 要求要有一个空参数的构造函数,个人觉得也可以让它们传递一个void 型指针)。
上面说过,在动态识别一个类时不需要pFirstClass 链表,因为类是沿着它的同宗路线比较(这也是一种隐含的链表)。但是动态创建就需要了,因为它也不知道自己是什么类型,因此要遍历pFirstClass 链表中所有的已知的类,直到找到与自己相符的类型。如果查找成功则通过指针调用初始化函数来创建对象(指针为NULL 则不能创建),否则就无法动态创建。
在强调一点:这是MFC--不是C++,所有的类都是从COject 继承而来(个别类除外),因此他们如果存在就一定被保存在pFirstClass 链表中。如果你要是另起炉灶,随便派生自一个类,又使用了DEALARE_DYNCREATE 和IMPLEMENT_DYNCREATE 宏,那情况就糟糕了,pFirstClass 链表可能被彻底的破坏,那COject 的什么特性就都没了(切记)
4 序列化 Serialize
序列化就是要支持对象的动态存储与恢复(像打开一个网页,然后自动下载一个未知的程序到你电脑上运行……)。个人感觉,序列化和动态创建应该是面向对象数据库的核心!
以前数据存放之后就是死的数据,数据的操作要靠其他的程式来支持--就是那种传统的数据组织方式。面向对象数据库则比较有意思:数据放进去之后,再拿出来的话还可以自己活动,甚至自己生长、演化!
序列化中关键的技术是:在保存数据本身的同时,还要保存数据的行为--也就是对象的行为(或者是类的信息)。有了数据的行为就好办,这就又回到了上面的对象动态创建问题。关于对象的行为保存细节很多,但基本上就数据库中数据组织的那一套(关键是要看怎么应付复杂的硬件环境)。
其实面向对象东西只是更高一层的抽象,为了简化大型项目开发的难度,但最终还是要回到过程性的操作上--毕竟所有的程序都运行在冯.诺依曼机器上(这本身就是顺序运行的机器)。在MFC 中更是许多与问题无关的细节,让开发人员集中精力解决最核心的问题。
5 消息

消息是windows 程序开发的核心概念,程序的行为不再是像以前那样--用户只需要安排好事情的内容,不需要安排什么时候去做事情。在收到系统通知的时候就去做事情,没收到通知的话就先歇着(看来机器就是喜欢偷懒呢)。这很像我们人的行为:如果没有人给我分配任务,我就休息。
消息在MFC 中的传播机制很复杂(我自己是没高清楚),一般可以分2 种类型:一是只能向父类传播的消息,还有可以横向传播的消息。向父类传播的消息的行为很简单(和动态识别的路线相似),一路向上直到CCmdTarget ,就完成任务了。可以横向传播的消息有固定的传播路线(我不知道为什么要按这个顺序),在书中有具体的描述,最后也是到CCmdTarget,但是中途要走了很多弯路(走弯路是为了让别人拦截)。
关于横向消息在不同类之间的跳跃机制还没有搞清楚,我自己估计是借助了几个类中变量(不知道对不对),代码如下:
class CWinApp : public CWinThread { public:
    CWinApp *m_pCurrentWinApp;     CWnd * m_pMainWnd; }
class CFrameWnd : public CWnd { public:
    CView *m_pViewActive; }
class CView : public CWnd { public:
    CDocument * m_pDocument; }
借助m_pCurrentWinApp, m_pMainWnd, m_pViewActive, m_pDocument 可以轻易地实现在不同类之间的移动,因此实现消息的固定传播路线也就比较容易了。
注意:由于MFC 是一种Application Framework ,它之间的类是强耦合的,类之间是有生命联系的,因此可以融为一体,所以可以借助m_pCurrentWinApp, m_pMainWnd, m_pViewActive, m_pDocument 的相互配合(就象人的各个器官相互协作一样),达到目的。
第三章小结
大的方面说不清楚,只是想提醒一下那几个宏的用法。以前在C 中总是想让宏模拟函数的行为,使用的时候也照着函数的习惯用。比如:
#define swap(a,b) do { / long t = a; a = b; b = t; / }while(0)
void main(void) { int a = 1, b = 2;   swap(a, b);
}
我们不自觉地就在swap(a, b)后面加了‘;’(好象它就是一个函数),但MFC 中的宏并不是这样。在MFC 中要严格按照宏的定义使用,否则可能存在危险。当然,如果能用向导生成就最好了,省得烦心。另外RUNTIME_CLASS(class_name) 用于获得类的CRuntimeClass 静态成员。
第四章 VC++的集成开发环境
这一章不知道所什么,经常使用吧。
第五章总观Application Framework
这里也不知道说些什么,太抽象了,我想每个人的体会可能都不一样。
第六章 MFC 程序的生死因果
这一章只要能把294 页的流程图搞清楚了就差不多了(中文简体第2 版),当然也要把消息机制融入其中(以及回调函数)。

第七章简单而完整:MFC 骨干程序

介绍了一般框架所需要的类(如图):

第八章 Document-View 深入探讨

《深入浅出MFC》学习笔记相关推荐

  1. 第二行代码学习笔记——第六章:数据储存全方案——详解持久化技术

    本章要点 任何一个应用程序,总是不停的和数据打交道. 瞬时数据:指储存在内存当中,有可能因为程序关闭或其他原因导致内存被回收而丢失的数据. 数据持久化技术,为了解决关键性数据的丢失. 6.1 持久化技 ...

  2. 第一行代码学习笔记第二章——探究活动

    知识点目录 2.1 活动是什么 2.2 活动的基本用法 2.2.1 手动创建活动 2.2.2 创建和加载布局 2.2.3 在AndroidManifest文件中注册 2.2.4 在活动中使用Toast ...

  3. 第一行代码学习笔记第八章——运用手机多媒体

    知识点目录 8.1 将程序运行到手机上 8.2 使用通知 * 8.2.1 通知的基本使用 * 8.2.2 通知的进阶技巧 * 8.2.3 通知的高级功能 8.3 调用摄像头和相册 * 8.3.1 调用 ...

  4. 第一行代码学习笔记第六章——详解持久化技术

    知识点目录 6.1 持久化技术简介 6.2 文件存储 * 6.2.1 将数据存储到文件中 * 6.2.2 从文件中读取数据 6.3 SharedPreferences存储 * 6.3.1 将数据存储到 ...

  5. 第一行代码学习笔记第三章——UI开发的点点滴滴

    知识点目录 3.1 如何编写程序界面 3.2 常用控件的使用方法 * 3.2.1 TextView * 3.2.2 Button * 3.2.3 EditText * 3.2.4 ImageView ...

  6. 第一行代码学习笔记第十章——探究服务

    知识点目录 10.1 服务是什么 10.2 Android多线程编程 * 10.2.1 线程的基本用法 * 10.2.2 在子线程中更新UI * 10.2.3 解析异步消息处理机制 * 10.2.4 ...

  7. 第一行代码学习笔记第七章——探究内容提供器

    知识点目录 7.1 内容提供器简介 7.2 运行权限 * 7.2.1 Android权限机制详解 * 7.2.2 在程序运行时申请权限 7.3 访问其他程序中的数据 * 7.3.1 ContentRe ...

  8. 第一行代码学习笔记第五章——详解广播机制

    知识点目录 5.1 广播机制 5.2 接收系统广播 * 5.2.1 动态注册监听网络变化 * 5.2.2 静态注册实现开机广播 5.3 发送自定义广播 * 5.3.1 发送标准广播 * 5.3.2 发 ...

  9. 第一行代码学习笔记第九章——使用网络技术

    知识点目录 9.1 WebView的用法 9.2 使用HTTP协议访问网络 * 9.2.1 使用HttpURLConnection * 9.2.2 使用OkHttp 9.3 解析XML格式数据 * 9 ...

  10. 安卓教程----第一行代码学习笔记

    安卓概述 系统架构 Linux内核层,还包括各种底层驱动,如相机驱动.电源驱动等 系统运行库层,包含一些c/c++的库,如浏览器内核webkit.SQLlite.3D绘图openGL.用于java运行 ...

最新文章

  1. C++动态链接库dll及静态链接库lib制作及使用教程
  2. torch标记维度最大
  3. GraphQL:Descriptor Attributes
  4. C#开发笔记之12-如何用C#统计子字符串出现的次数?
  5. ES6的Promise -- 逻辑执行的顺序
  6. php 检测 变量是否设置,PHP中检测一个变量是否有设置的函数是什么?
  7. 回馈顾客, 活动搞起 --- 策略模式
  8. excel工具栏隐藏了怎么办_Pixel Studio 像素软件教程之工具栏介绍
  9. python绘图之散点图
  10. 2019计算机单招试题,(完整版)2019高职单招计算机类专业练习卷
  11. [转]《牵一只蜗牛去散步》台湾 张文亮
  12. 最全互联网Linux工作规划!
  13. c# NPOI 导出Excel 冻结窗格
  14. 1.3数据库系统的组成及特点
  15. 青龙面板基本脚本运行必装依赖 一键式安装脚本安装依赖 2023年3月28日
  16. 这里记录几个国外的网站
  17. 添加按钮声音nbsp;nbsp;播放声音
  18. python 内置模块之os、sys、shutil
  19. 中小型企业HR如何做培训计划?
  20. LeetCode算法,每日一题,冲击阿里巴巴,day3

热门文章

  1. 毕设日志2019.4.2——tf版本的faster rcnn的代码运行
  2. 【每日一包0027】statuses
  3. 人人都能学会的python编程教程1:第一行代码
  4. dcloud会员激活mui
  5. Unity3D动画面板编辑器状态属性对照表
  6. Android Fragment 使用详解
  7. Linux Bash Shell中的特殊参数含义
  8. xml文件中若没有子节点,则删除文件
  9. TCP/IP及内核参数优化调优(转)
  10. nginx经过多层代理后获取真实来源ip