深入浅出mfc读书笔记
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
由于_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 开始遍历)。
调函数一般都是由 windows 系统来调用,不是用户自己调用。在用户使用 bsearch 函数时, 用户自己定义的 cmp 函数也是由 C 函数库来调用,不是自己调用。
回调函数的概念虽然在 C 语言中就已经存在,但使用的范围远没有 windows 中的这么广(其 实在设计接口时,遇到某些有共性的未知操作就可以用传递一个函数的方法解决——例如遍 历某个未知集合中的每个元素)。
在 windows 中程序设计的主要任务就是对自己感兴趣的消息做出相应的处理:程序等待某 个特定消息的发生,然后针对该消息做出特定的操作,如此而已!
第二章 C++的重要性质
面向对象有三个核心概念:封装,继承,多态。封装和继承这里不想细说,主要讲一下多态。
很多书里都说多态是面向对象的核心(也不知道对不对)。在 C++中支持多态的关键技术就 是虚函数。虚函数的有些特征很怪异,这主要是和传统 C 函数比较而言的(如果没有传统 的函数概念也就不会觉得奇怪了)。
因此,回调函数就成了“以消息为基础的事件驱动系统”系统平台上程序开发的核心!
为了向面向对象思想看齐,一般把回调函数也设计成类的成员。又因为回调函数有固定格式, 不能随便修改,因此在类中要把它声明为 static 类型函数(这是利用了 C++编译器不会为类 中 static 函数添加 this 指针参数的隐含特征)。
我自己把这种树叫做逆树(和通常的树相反,好象是反物质一类的东东)。 其实在所有关于 指针的数据结构中都有这种逆*的存在。你可以想象在一个单向链表中,从一个结点移动到 后一个结点时,就回不到之前的结点了(除非你另外保存了它的地址)。在现实中也有很多 这种情况:你可以知道你所认识的人,但却很难知道所有认识你的人——这是指针的不可逆 性造成的。
我觉得这里的识别有两种级别,打个比方:X 是某个人,还是具有某个人的血统? 这个问题该问谁,怎么问?问 X 的爸爸妈妈、爷爷奶奶还是 X 自己?如果李四想知道自己
身上是否有李世民的血统,是要亲自问李世民吗(他怎么会知道自己有多少后代)?
正确的办法是: 1. 把 X 设为李四。 2. X 是不是李世民? 3. 如果 X 是就停止,并输出结果是。 4. 如果 X 不是,但 X 有爸爸,就 X 设成 X 的爸爸,然后转到 2。 5. 如果 X 没有爸爸就停止,输出结果否。
};
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass *pNewClass) {
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass; CRuntimeClass::pFirstClass = pNewClass; }
补充一点:定义 CObject 类时要手工生成 pFirstClass 链表和手工初始化 CRuntimeClass。
3 动态创建
MFC 也定义了 2 个宏:DEALARE_DYNCREATE 和 IMPLEMENT_DYNCREATE。
要动态的生成对象,首先要知道对象的初始化函数,在 MFC 中采用在 CRuntimeClass 中保 存函数指针的方法来实现。保存指针等操作的代码也是在宏中加入的(MFC 要求要有一个 空参数的构造函数,个人觉得也可以让它们传递一个 void 型指针)。
很多书上用什么动态绑定来解释虚函数(还保存了一张什么虚函数表),我觉得这可能是因 为他们了解一些 C++编译器的实现细节。如果他们不知道 C++编译器怎么实现的,他们怎 么就知道就要用虚函数表来实现虚函数呢(而且用户也不可能知道每个编译器的细节)?
虽然 C++编译器是一个黑盒子,但我们仍然可以用 C 语言中方法来模拟一个虚函数。我自 己喜欢把虚函数看作一个函数指针,该指针初始值为 NULL,每遇到函数的定义时就把该指 针设置为新定义的函数的地址(当然派生类从基类中继承了这个函数指针)。这样,用户在 通过函数指针调用虚函数的行为就很清楚了(如果不熟悉指针就不好办了)。又由于函数是 类的成员,不是对象的成员,因此把虚函数看作 static 型的函数指针更准确。
所以 C 程序员在调用一个函数的时候就根本想不出它会有什么出格的行为!
在 C++中就不一样了,特别是虚函数,有时候简直搞不清楚它到底是调用了哪个函数(真是 麻烦)!
这种情况是由 C++是一个面向对Baidu Nhomakorabea的语言性质决定的。如果你还是用 C++编写 C 程序,那 么它还是一个高级的汇编语言;但是如果你用 C++编写(特别是有虚函数的)面向对象程序 就不是那么回事了!
size_t n, size_t size, int (*cmp)(const void *keyval, const void *datum)); 想一想开发 bsearch 函数的人怎么会知道两个元素的是什么,怎么比较大小呢?因此就必须 留给用户要自己定义 cmp 函数了! 回调函数一般都有固定的格式(不知道是否会用变参数的情况),不然可能会发生错误。回
我比较感兴趣的是 AFX_CLASSINIT 的初始化过程,代码如下:
static AFX_CLASSINIT _init_classname(class_name::classclass_name);
struct AFX_CLASSINIT { AFX_CLASSINIT(CRuntimeClass *pNewClass);
void main() {
A *pa = new B; pa->display(); }
执行的结果却打印是:class B
让人感觉不解的地方就是 pa 明明是类 A 的指针,却是执行了类 B 的函数(不可原谅)!!! 其实有这种感觉的人在不知不觉中就犯了一个形而上的错误:用 C 语言的函数行为来套用 display()的行为。在此我想提醒一点:把 C++当作一个新的语言,C 只是参考,不是金科玉 律,切记!!!
第三章 MFC 六大关键技术之仿真
其实除了消息外,其他的几个技术细节都可以看作是面向对象语言的特征。例如:对象的产
生过程、动态识别 ……。动态识别、动态创建、序列化特征已经在 JAVA 等新的面向对象 语言中得到支持了。如果不想了解编译器的实现细节的话,也可以不看。MFC 本身特有的 东西应该是消息的传播机制。当然这里还是要全部总结一下了(毕竟也是这本书最有特色的 地方了)。 1 对象创建 MFC 中所有的类都继承自 CObject,创建对象时要考虑其父类的创建。个人觉得是这样一个 规则(不知道对不对):创建对象之前要先创建父类,除非它没有父类!构造的函数的调用 规则也是这样:如果有就先调用父类的构造函数(这是 MFC,不是 C++,不考虑多重继承 的情况)。 这就像人类的繁衍:一个人要出生,他的爸爸妈妈肯定要先出生,除非他是第一个进化成人 类的(或者是人工合成的)。 2 运行时类型识别
MFC 为 了 隐 藏 类 的 家 族 树 的 实 现 细 节 , 定 义 了 2 个 宏 : DEALARE_DYNAMIC 和 IMPLEMENT_DYNAMIC。DEALARE_DYNAMIC 用于定义变量,IMPLEMENT_DYNAMIC 则进行相应的初始化,宏的具体细节可以参考书中代码。
回调函数在 windows 开发中得到推广应该是由其“以消息为基础的事件驱动系统”本质决 定的。用用户要实现某个操作,但是不知道什么时候开始执行(因为不知道什么时候能收到 相应的消息);系统则知道什么时候触发操作(因为消息由系统发出),但是又不知道操作的 具体细节(操作是用户自己定义的)。在这种时候回调函数就成了用户和系统之间沟通的桥 梁——用户自己定义操作的细节,但是由系统在适当的时刻帮助调用。
在 MFC 中保存了一棵类的家族树,CObject 是根结点,其他的类都是他的后代(有几个特 殊的除外,如: CPoint 等)。由于类的家族树存放的是类的信息——不是对象的信息,因此 只需要保存一个就够了,所以 MFC 将这棵树保存为 static 类型。
MFC 类的家族树和数据结构中的树并不相同,普通的树通过跟结点就可以访问所有的结点 (包括叶子)。但在 MFC 中却不行——它只能逆向地从叶子结点向根结点方向访问(从父 结点访问不到子结点)。
《深入浅出 MFC》学习笔记
学习时间:2006.3.19 - 2006.2.30 柴树杉 2006.2.30 整理于 VisionTek
第一章 win32 基本程序概念
windows 是一个“以消息为基础的事件驱动系统”。当系统内核捕捉到外围设备发生的事件 后,将以一种特定的消息传递出去。而用户程序在接收到相应的消息后再做出相应的处理(否 则系统以默认函数处理)。处理窗口过程的一般是窗口函数(window procedure)。Windows 程序的执行流程如上图。 窗口函数习惯上称作回调函数,回调函数类似于 C 语言中 bsearch(二分法查找)函数的 cmp (用于比较两个元素的大小)参数: // #include <stdlib.h> void *bsearch(const void *key, const void *base,
上面说过,在动态识别一个类时不需要 pFirstClass 链表,因为类是沿着它的同宗路线比较(这 也是一种隐含的链表)。但是动态创建就需要了,因为它也不知道自己是什么类型,因此要 遍历 pFirstClass 链表中所有的已知的类,直到找到与自己相符的类型。如果查找成功则通过 指针调用初始化函数来创建对象(指针为 NULL 则不能创建),否则就无法动态创建。
在强调一点:这是 MFC——不是 C++,所有的类都是从 COject 继承而来(个别类除外), 因此他们如果存在就一定被保存在 pFirstClass 链表中。如果你要是另起炉灶,随便派生自一 个类,又使用了 DEALARE_DYNCREATE 和 IMPLEMENT_DYNCREATE 宏,那情况就糟 糕了,pFirstClass 链表可能被彻底的破坏,那 COject 的什么特性就都没了(切记)!!!
以前很多人用 C 语言(现在也有好多人用 C 入门),对 C 的执行机制很熟悉。C 语言是一种 高级的汇编语言,写一段代码就是一段代码,编译器不会暗中给你做什么手脚。即使编译器 做也就是在初始化和退出的时候调用一些函数,这些用户都知道(fork、exec、exit……)。
关于函数这一块也一样: 函数参数是值传递——数组除外(数组传地址),为了支持变参数,参数是从右到左进栈, 函数名就代表一个函数的入口地址,比较复杂一些的就是函数指针。
例如:
class A { public:
virtual void display() { cout << "class A" << endl; } };
class B: public A { public:
virtual void display() { cout << "class B" << endl; } };
这很有点像一种静态的初始化操作——AFX_CLASSINIT 在运行之前已经被自动地完成了。 当然 AFX_CLASSINIT 和静态初始化还是有些区别的:我感觉静态初始化应该是在编译时 被调用,而不是在执行时被调用。这应该算是 C++中一些很晦涩的技巧吧
pFirstClass 链 表 是 通 过 AFX_CLASSINIT 自 动 初 始 化 的 。 但 是 class_name::classclass_name::m_pBaseClass 指向的同宗链表则完全是手工初始化的(通过宏 传递的参数)。在同宗链表中每个类和它的父类都可以用确定名称直接访问(静态的类别型 录网中的每个 CRuntimeClass 都可以通过一个确定的名称直接访问——不必要从 pFirstClass 开始遍历)。
调函数一般都是由 windows 系统来调用,不是用户自己调用。在用户使用 bsearch 函数时, 用户自己定义的 cmp 函数也是由 C 函数库来调用,不是自己调用。
回调函数的概念虽然在 C 语言中就已经存在,但使用的范围远没有 windows 中的这么广(其 实在设计接口时,遇到某些有共性的未知操作就可以用传递一个函数的方法解决——例如遍 历某个未知集合中的每个元素)。
在 windows 中程序设计的主要任务就是对自己感兴趣的消息做出相应的处理:程序等待某 个特定消息的发生,然后针对该消息做出特定的操作,如此而已!
第二章 C++的重要性质
面向对象有三个核心概念:封装,继承,多态。封装和继承这里不想细说,主要讲一下多态。
很多书里都说多态是面向对象的核心(也不知道对不对)。在 C++中支持多态的关键技术就 是虚函数。虚函数的有些特征很怪异,这主要是和传统 C 函数比较而言的(如果没有传统 的函数概念也就不会觉得奇怪了)。
因此,回调函数就成了“以消息为基础的事件驱动系统”系统平台上程序开发的核心!
为了向面向对象思想看齐,一般把回调函数也设计成类的成员。又因为回调函数有固定格式, 不能随便修改,因此在类中要把它声明为 static 类型函数(这是利用了 C++编译器不会为类 中 static 函数添加 this 指针参数的隐含特征)。
我自己把这种树叫做逆树(和通常的树相反,好象是反物质一类的东东)。 其实在所有关于 指针的数据结构中都有这种逆*的存在。你可以想象在一个单向链表中,从一个结点移动到 后一个结点时,就回不到之前的结点了(除非你另外保存了它的地址)。在现实中也有很多 这种情况:你可以知道你所认识的人,但却很难知道所有认识你的人——这是指针的不可逆 性造成的。
我觉得这里的识别有两种级别,打个比方:X 是某个人,还是具有某个人的血统? 这个问题该问谁,怎么问?问 X 的爸爸妈妈、爷爷奶奶还是 X 自己?如果李四想知道自己
身上是否有李世民的血统,是要亲自问李世民吗(他怎么会知道自己有多少后代)?
正确的办法是: 1. 把 X 设为李四。 2. X 是不是李世民? 3. 如果 X 是就停止,并输出结果是。 4. 如果 X 不是,但 X 有爸爸,就 X 设成 X 的爸爸,然后转到 2。 5. 如果 X 没有爸爸就停止,输出结果否。
};
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass *pNewClass) {
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass; CRuntimeClass::pFirstClass = pNewClass; }
补充一点:定义 CObject 类时要手工生成 pFirstClass 链表和手工初始化 CRuntimeClass。
3 动态创建
MFC 也定义了 2 个宏:DEALARE_DYNCREATE 和 IMPLEMENT_DYNCREATE。
要动态的生成对象,首先要知道对象的初始化函数,在 MFC 中采用在 CRuntimeClass 中保 存函数指针的方法来实现。保存指针等操作的代码也是在宏中加入的(MFC 要求要有一个 空参数的构造函数,个人觉得也可以让它们传递一个 void 型指针)。
很多书上用什么动态绑定来解释虚函数(还保存了一张什么虚函数表),我觉得这可能是因 为他们了解一些 C++编译器的实现细节。如果他们不知道 C++编译器怎么实现的,他们怎 么就知道就要用虚函数表来实现虚函数呢(而且用户也不可能知道每个编译器的细节)?
虽然 C++编译器是一个黑盒子,但我们仍然可以用 C 语言中方法来模拟一个虚函数。我自 己喜欢把虚函数看作一个函数指针,该指针初始值为 NULL,每遇到函数的定义时就把该指 针设置为新定义的函数的地址(当然派生类从基类中继承了这个函数指针)。这样,用户在 通过函数指针调用虚函数的行为就很清楚了(如果不熟悉指针就不好办了)。又由于函数是 类的成员,不是对象的成员,因此把虚函数看作 static 型的函数指针更准确。
所以 C 程序员在调用一个函数的时候就根本想不出它会有什么出格的行为!
在 C++中就不一样了,特别是虚函数,有时候简直搞不清楚它到底是调用了哪个函数(真是 麻烦)!
这种情况是由 C++是一个面向对Baidu Nhomakorabea的语言性质决定的。如果你还是用 C++编写 C 程序,那 么它还是一个高级的汇编语言;但是如果你用 C++编写(特别是有虚函数的)面向对象程序 就不是那么回事了!
size_t n, size_t size, int (*cmp)(const void *keyval, const void *datum)); 想一想开发 bsearch 函数的人怎么会知道两个元素的是什么,怎么比较大小呢?因此就必须 留给用户要自己定义 cmp 函数了! 回调函数一般都有固定的格式(不知道是否会用变参数的情况),不然可能会发生错误。回
我比较感兴趣的是 AFX_CLASSINIT 的初始化过程,代码如下:
static AFX_CLASSINIT _init_classname(class_name::classclass_name);
struct AFX_CLASSINIT { AFX_CLASSINIT(CRuntimeClass *pNewClass);
void main() {
A *pa = new B; pa->display(); }
执行的结果却打印是:class B
让人感觉不解的地方就是 pa 明明是类 A 的指针,却是执行了类 B 的函数(不可原谅)!!! 其实有这种感觉的人在不知不觉中就犯了一个形而上的错误:用 C 语言的函数行为来套用 display()的行为。在此我想提醒一点:把 C++当作一个新的语言,C 只是参考,不是金科玉 律,切记!!!
第三章 MFC 六大关键技术之仿真
其实除了消息外,其他的几个技术细节都可以看作是面向对象语言的特征。例如:对象的产
生过程、动态识别 ……。动态识别、动态创建、序列化特征已经在 JAVA 等新的面向对象 语言中得到支持了。如果不想了解编译器的实现细节的话,也可以不看。MFC 本身特有的 东西应该是消息的传播机制。当然这里还是要全部总结一下了(毕竟也是这本书最有特色的 地方了)。 1 对象创建 MFC 中所有的类都继承自 CObject,创建对象时要考虑其父类的创建。个人觉得是这样一个 规则(不知道对不对):创建对象之前要先创建父类,除非它没有父类!构造的函数的调用 规则也是这样:如果有就先调用父类的构造函数(这是 MFC,不是 C++,不考虑多重继承 的情况)。 这就像人类的繁衍:一个人要出生,他的爸爸妈妈肯定要先出生,除非他是第一个进化成人 类的(或者是人工合成的)。 2 运行时类型识别
MFC 为 了 隐 藏 类 的 家 族 树 的 实 现 细 节 , 定 义 了 2 个 宏 : DEALARE_DYNAMIC 和 IMPLEMENT_DYNAMIC。DEALARE_DYNAMIC 用于定义变量,IMPLEMENT_DYNAMIC 则进行相应的初始化,宏的具体细节可以参考书中代码。
回调函数在 windows 开发中得到推广应该是由其“以消息为基础的事件驱动系统”本质决 定的。用用户要实现某个操作,但是不知道什么时候开始执行(因为不知道什么时候能收到 相应的消息);系统则知道什么时候触发操作(因为消息由系统发出),但是又不知道操作的 具体细节(操作是用户自己定义的)。在这种时候回调函数就成了用户和系统之间沟通的桥 梁——用户自己定义操作的细节,但是由系统在适当的时刻帮助调用。
在 MFC 中保存了一棵类的家族树,CObject 是根结点,其他的类都是他的后代(有几个特 殊的除外,如: CPoint 等)。由于类的家族树存放的是类的信息——不是对象的信息,因此 只需要保存一个就够了,所以 MFC 将这棵树保存为 static 类型。
MFC 类的家族树和数据结构中的树并不相同,普通的树通过跟结点就可以访问所有的结点 (包括叶子)。但在 MFC 中却不行——它只能逆向地从叶子结点向根结点方向访问(从父 结点访问不到子结点)。
《深入浅出 MFC》学习笔记
学习时间:2006.3.19 - 2006.2.30 柴树杉 2006.2.30 整理于 VisionTek
第一章 win32 基本程序概念
windows 是一个“以消息为基础的事件驱动系统”。当系统内核捕捉到外围设备发生的事件 后,将以一种特定的消息传递出去。而用户程序在接收到相应的消息后再做出相应的处理(否 则系统以默认函数处理)。处理窗口过程的一般是窗口函数(window procedure)。Windows 程序的执行流程如上图。 窗口函数习惯上称作回调函数,回调函数类似于 C 语言中 bsearch(二分法查找)函数的 cmp (用于比较两个元素的大小)参数: // #include <stdlib.h> void *bsearch(const void *key, const void *base,
上面说过,在动态识别一个类时不需要 pFirstClass 链表,因为类是沿着它的同宗路线比较(这 也是一种隐含的链表)。但是动态创建就需要了,因为它也不知道自己是什么类型,因此要 遍历 pFirstClass 链表中所有的已知的类,直到找到与自己相符的类型。如果查找成功则通过 指针调用初始化函数来创建对象(指针为 NULL 则不能创建),否则就无法动态创建。
在强调一点:这是 MFC——不是 C++,所有的类都是从 COject 继承而来(个别类除外), 因此他们如果存在就一定被保存在 pFirstClass 链表中。如果你要是另起炉灶,随便派生自一 个类,又使用了 DEALARE_DYNCREATE 和 IMPLEMENT_DYNCREATE 宏,那情况就糟 糕了,pFirstClass 链表可能被彻底的破坏,那 COject 的什么特性就都没了(切记)!!!
以前很多人用 C 语言(现在也有好多人用 C 入门),对 C 的执行机制很熟悉。C 语言是一种 高级的汇编语言,写一段代码就是一段代码,编译器不会暗中给你做什么手脚。即使编译器 做也就是在初始化和退出的时候调用一些函数,这些用户都知道(fork、exec、exit……)。
关于函数这一块也一样: 函数参数是值传递——数组除外(数组传地址),为了支持变参数,参数是从右到左进栈, 函数名就代表一个函数的入口地址,比较复杂一些的就是函数指针。
例如:
class A { public:
virtual void display() { cout << "class A" << endl; } };
class B: public A { public:
virtual void display() { cout << "class B" << endl; } };