昨天,又遇同学谈virtual的什么早期绑定、动态联编不懂. 我和他交流了一会,才知道.哦,原来是教科书上,那"路边抄" 的概念把他迷惑了.
1.静态联编:联编工作出现在编译连接阶段,这种联编又称早期联编,因为这种 联编过程是在程序开始运行之前完成的。 2.编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能 确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序 运行时进行,这种在程序运行时进行联编工作被称为动态联编,或称动态束定, 又叫晚期联编.
针对第二个概念:我谈一下我的理解. 并不是真的好神奇,真的Danymic.只是 class_object_pointer->samename_member_function(param); 这样的方法执行,推给了vptr[serialnumber](这里指虚函数机制实现的虚表指针, 和序列号,具体自己参看资料[侯sir翻译的ITC++OM不错]) 而vptr[serialnumber]指向的实际函数根据你定义的类类型来决定. 而不是以你定义的指针类型来决定的. 所以你: baseclass* pointer = new derivedclass; 实际上呢: pointer->samename_number_function(); 就调用了 现时的vptr[serialnumber]. 因为对[客户]来说,他只要位置信息,不知位置到底是什么,那须看生成的对象真正指的 什么. 而在虚拟机制下,对象默认绑定自己的number_function. 而所谓的早期绑定,只不过是编译器也难做人啊..... 你就一句: pointer->samename_member_function(); 编译器工作方式是:调用指针的类类型的成员函数,而不是你生出的对象的类的类型的 成员函数.(你想:这里的指针信息同于c等语言中的是重要的,也是操作的依据,当然也 是基本类指针不可访问继承类普通成员之依据.)
需要强调的是:对象是否new出的也导致所谓Danymic编译器实际动作的不同.
*************************************************************************** 兼批:满处的作秀 ************************************************************************** 对于虚函数不谈其它方面的好处: 譬如在COM里它无巧不成书的使得编译器能够编译出符合COM规范的接口要求的内存布局, 还有重载机制. 然在导致它产生的继承类和基本类之间确定同名函数调用要求的情形下,我觉得所谓的 Danymic不过是一件花衣裳.给人错觉.看看例子:
------------------------------------------------------ #include <iostream> #include <stdio.h>
using namespace std;
struct CA{ void fook(void){cout<<"I am programming."<<endl;} };
struct CB:public CA{ void fook(void){cout<<"I am sleeping."<<endl;} };
int main(void) { CA* p_cca = new CA; p_cca->fook(); delete p_cca; getchar();
CB* p_ccb = new CB; CB* p_ccb_ =new CB;
p_ccb->fook(); getchar();
p_cca = p_ccb; p_cca->fook(); //访问不到希望的CA之fook(); getchar();
p_ccb_->fook(); getchar();
delete p_ccb; delete p_ccb_;
return 0; }
上面的例子经常被人拿来,表现传统机制的不足. 你需要一会儿访问CA之fook,一会儿访问CB之fook的能力. 那么你完全可以: CB* p_ccb = new CB; 然后: p_ccb->fook(); //访问CB之fookv...最好都表达成:p_ccb->CB::fook(); p_ccb->CA::fook(); 我们不是做到了吗. 只是转个角度而已.为什么非得从下向上看,不可从上向下看呢?
有了虚拟,实现所谓的Danymic 它的内存是跟传统机制下的是天壤之别.多了一个'没财气的百宝箱', 还有一把'锋利的剑?'(COM是离不开它的). 可是作用呢?只是实现臆测中的好处. 它将同名的函数默认绑定到自己的fook上 #include <iostream.h> #include <stdio.h>
using namespace std;
struct CA{ virtual void fook(void){cout<<"I am programming."<<endl;} };
struct CB:public CA{ virtual void fook(void){cout<<"I am sleeping."<<endl;} };
int main(void) { CA* p_cca = new CA; CB* p_ccb = new CB;
p_cca->fook(); p_ccb->fook(); getchar(); p_ccb->CA::fook(); getchar(); delete p_cca; p_cca = p_ccb; p_cca->fook(); getchar(); delete p_ccb;
return 0; }
但是若你想调用基本类的fook你得p_ccb->CA::fook();
大家自己想想: 在调用的到底是什么同名函数的问题上,所谓的虚拟(Danymic)真的比传统 的解决方式不见得优秀在哪里.对客户来说,只是角度不同了.虽然不用virtual的方法 是笨了一些,因为为了用兼顾,你一般得确定你的对象指针生命期中,要不要访问 继承类的成员.
但在C++运算符重载和便于实现组件模型上表现出的能力的确称的上是精绝无比啊.(不过 偶觉还得说它设计的绝,不如说编译器绝呢.)
反正我绝的大家谈上virtual,就那上面的例子说明它美妙之处的做法很是牵强. 而且动不动来个什么早期绑定、后期绑定(执行时确定)的迷惑人的术语.像做秀.
其实呢?一切还不是在编译阶段就有预谋,他的罪魁祸首就是编译器. 在同名函数的问题上,若编译器增加编译时辨别意图的方式,还不是一样能够实现. 如: CA* p_cca = new CB; 我编译器就将p_cca->fook的绑定CB的.不照样可以.
--------------------------付思考题6道--------------------------
下面的每个以I开头的表示接口(C++中的纯虚类) 以C开头的表示类 从右到左顺序继承 遇","表示多继承 问: 最后的类生成的对象中有几个vptr.
----------------- 1. CD:IC:IB:IA ----------------- 2. CD:IC:IB,IA ----------------- 3. CC:IB:IA ----------------- 4. CC:IB,IA ----------------- 5. CD:IC:IA,IB ---------------- 6. 问CF的vptr.共四个条件 CF:IE IE:ID ID:IC,IB IB:IA:IUknown -----------------------

|