前天发表的《MFC六大关键技术之剖析(三)》后,就遇到不少朋友的提问。而这些问题都让我很惊讶——不知问的是什么?后来我算是明白了,因为有一个朋友问:“为了动态创建对象,要干那么多的事情吗?究竟怎样才算动态创建对象?”
我知道了他还没有弄懂在MFC中动态创建的含义。在MFC的动态创建,比我们平时用new动态分配内存有着更深一层的意义。MFC的动态创建是在程序运行之后,在程序并未预测到将会获得什么样的类名,比如程序打开一个文件,或接受一个来自键盘的字符串,MFC程序能跟据你的输入,产生一个对象。能够完成这一功能的关键因素是CRuntimeClass链表。有了这个链表,我们就可以对程序说:“帮我‘生产’一个XXX。”程序预先并不知道XXX是个什么样的对象。它会回答:“我得在我的CRuntimeClass笔记本里查找一下,好,找到了,是这样的东西,可以根据CRuntimeClass写的要求生产了。”
关于动态创建的问题,你在我的MFC六大关键技术之剖析(四)——永久保存,会有彻头彻尾的理解。但我现在更关心的是另外一个问题。
刚才的问题让我陷入深深的回忆中,仿佛又回到了好几年以前。那时捧着一本C++程序,那个时候什么也不懂,竟然在“动态”与“静态”之间徘徊。每本书都很“清楚”地说:“编译链接之前的内存分配就是静态,程运行之后的分配就是动态!”
不说,我还有点明白,说了我更加不懂。因为我们只知道编译链接是语言的翻译过程,把源代码转换成计算机懂得的机器指令罢了。无论选择任何的编译器,最终目的只是更有效地生成一个可执行文件。既然是一个文件,程序还没有运行的时候,它是以.EXE的扩展名存储在磁盘中。
如果把编译链接和运行间断开来,程序在还没有载入运行的时候,它只是一个文件,关内存什么事?这还不算,绝大多数(几乎我看到的都是)书本上在讲多态性的时候,还讲到一种“运行时绑定”,它的意思就是“在这些重载的函数中,可一系列虚函数中,编译时还不确定调用哪个函数,运行时就知道了!”
“屁话!通通都是屁话!”在我编程多年以后,才发觉书上写得特错大错。试想一想,编译器都不能确定将会调用那个函数,那么程序运行之后又如何确定(当然,那些根据判断和返回值调等用的函数另当别论)。系统能确定吗,系统是一个冰冷的机器,加上一个来料加工,甚至更多是被我们程序去操纵的操作系统,它如何能知道你自己写程序直到编译时自己都弄不清楚的东西?其实在函数重载或虚函数当中,编译器早就用按规则改名,或创建虚函数表,清清楚楚地确定将要调用的函数。
静态与动态分配其实是一件很简单的事情。
我们在载入程序的时候,系统要把我们的程序代码载入内存。
在程序真正运行之前,程序要为我们的一系列函数或对象分配空间和初始化,如全局对象或静态变量,分配在静态存储区域内。在程序运行期间,那一块存储区跟程序本身联系在一起,我们不能删除它。如我们定义了一个全局变量int x=100;我们不能试图通过delete x;语句来释放它。那一块存储区域从程序的生与死早已由程序“冥冥注定”,不待我们的代码去判决。
局部对象也一样,它的生命周期是从左大括号开始,到右大括号结束。期间在栈上被创建,出了栈就结束,它的生命也由程序本身决定。
动态内存分配就不同了,它在堆上分配,堆是一块很大的内存。在上面分配内存就象军阀割据一样,一个军阀的地界,可能马上会被改写。
我们可以从从左大括号开始分配,但出了右大括号并不代表那块程序“死亡”——释放。 当然,我们也可以随时一句“delete 军阀1”让它即时变成可用内存。
动态内存分配在某些书上说成是用了new或者在堆分配的内存,甚至究其特点:分配的对象是没有名字的,如:int *pi=new int;(注意,pi是指针名字,不要与它所指向的对象相混,先分配一个没有名字的整形变量,再把变量地填给pi)。
但我觉得,之所以说成动态内存分配,是因为在堆分配内存非常灵活。就象一个动态数组,它可以在程序运行时增大、缩小、诞生、死亡,而这一切都在我们的程序语句之中去控制。
刚学c++时候,总觉得动态分配内存是一件很光荣的事。总认为动态就是自动的意思。后来才知道,对于内存分配,静态才是自动的——自动分配与回收。
而动态分配,总是手动的,你要在最后手动回收内存。
动态分配会影响程序的执行效率,就象干一件事情,需要某个“对象”的时候,才下命令去拿来,总不如静态分配,所有“对象”都备在身边,唾手可得。
我从来没有回头看过《MFC六大关键技术之剖析(三)》,因为我在写《一》的时候就不能确定会不会往下写。我觉得写出来的东西好象没有什么朋友想看,一般是跟几个帖罢了。我很想跟朋友们讲:了解六大关键技术对学MFC的朋友来说走的是一个捷径,要不,MFC编程对你来说永远是个恶梦。但我知道这是个人的看法,可能真有朋友就在那儿添加控件,添加消息映射而写出一个大好程序也是说不定的事情。