四、c++中的多态规则。一) c++中函数动态绑定规则。看下面的例子:
类TextWindow继承与类Window。我想程序运行的结果,大多数熟悉C++的人都会知道,win.oops()最终调用的是父类oops函数,而tWin->oops()调用的是子类TextWindow的函数。通过这个例子,我们先总结一下c++中的多态调用规则:第一、对于指针和引用类型,当消息调用的成员函数有可能被重写时,最终被选择调用的成员函数由消息接收者的动态类型确定(注意:在OO概念中,对某个对象成员函数进行调用,常常称为给该对象发送消息,该对象就是消息的接收者)。 如上例:tWin->oops(),由于tWin的动态类型是子类TextWindow,而不是Windows,所以tWin->oops()调用的是子类TextWindow的函数。第二、对于其它的变量,对虚拟函数调用绑定完全由该变量的静态类型确定(即该变量的声明),而不是该变量的真实类型确定。 如上例:win.oops(),由于win的声明类型为Window类,所以其结果调用的是父类oops函数。二) 探讨。 接下来,我们要看的问题是,在c++中,为什么对于多态规则(或者说是动态函数绑定规则),做出了两中不同的划分,即:只有指针与引用类型,才进行函数的后期动态绑定,也就是多态。这或许也是许多c++初学者非常迷惑的地方。这种规则的不一致性,的确给c++的语法造成一定的复杂性。而这在Java,或者C#中是没有的,后面我们会涉及到。 我们先来看例子。
在这里,如果我们假设,c++的函数动态绑定规则是一致的,看看会发生什么问题??? 现在win被声明为Window类型,然而其真实的类型为TextWindow(因为win=*tWinPtr),由于我们的假设,win现在是允许进行动态函数绑定的,所以当执行win.oops()时,实际上是调用子类TextWindow的成员函数。 现在,我们有必要来审视一下win变量的内存布局。由于win变量是在栈上声明的变量,其内存也是从栈进行分配(这是c++从c语言那里继承过来的优良特质,从栈上分配内存空间比动态分配内存有更好的执行速度),c++标准规定:给win变量分配内存空间的大小,由其静态的类型确定,即应该是Window类所使用的内存空间大小。在这种情况下,当执行win=*tWinPtr时,什么会发生?如下图:在默认的拷贝构造函数情况下,信息会出现丢失,这就是著名的slicing off现象。结果,变量cursorLocation在win的内存空间里丢失了。然而,问题是:在我们假设下,我们要求win.oops()导致TextWindow的成员函数调用,而在这个函数中,访问到的cursorLocation变量是不存在!win.oops()调用将导致内存违例! 到这里,我们可以总结一下:c++标准基于的其特定的内存分配规则,给出了以上,我们在前一节总结出的函数动态绑定规则。三) 深入。 当然,我们也可以说,c++也可以通过改变其内存分配规则,来给出一个一致性的函数动态绑定规则。比如:可以考虑在给win变量分配内存空间时,考虑其所有子类需求,然后分配最大数量的内存空间给win变量。这种做法可行性很差,对于编译器而言,需要扫描整个程序(确定该类的所有子类),才能确定最大的内存空间是多少。在使用类库或者框架的情况下,会引起整个类库,框架的重新编译,这是得不偿失的!而这种做法,在oo的语言中,基本上是没有的。这也是c++不得不基于其现有的内存管理机制,而对多态规则作出的不一致的解释。 对于这个c++现有的内存管理机制,我们如果从另外角度去理解的话,是很合理的。当win=*tWinPtr发生时,我们可以类似地认为:好比一个float类型的数赋给了一个interger类型的变量,其结果当然是float的值被截断了。 我们再来看其它语言,Java(或者C#)是怎么解决的。 最重要的一点是,在Java(C#)中只有引用的概念,所以在栈上声明的类的变量,只需要分配一个指针大小的内存空间就行了,而不需要给该变量分配空间来保存变量内容本身,这其实就是我们现在看到的c++中指针和引用的情况。
Powered by: C++博客 Copyright © 爱上龙卷风