};
void main(){
A *a = new B;
a->fun();
}
打印结果为2
如果class A中将关键字virtual去掉,则打印结果为1.
总结:只有在基类中将函数声明为虚函数,才能实现多态。
二. C++多态的实现原理
虚函数如何根据对象的不同而调用不同的函数呢?
如果一个类中有虚函数存在,则编译器会在类中插入一个指针,叫做vptr指针,该指针指向一个表,叫做vtbl。vtbl的作用是保存每一个虚函数的地址。看这段代码:
A *a = new B;
a->fun();
执行这段代码时,首先取出vptr的值,这个值是vtbl的地址。根据该值找到vtbl,在表vtbl中取得该虚函数fun()的地址,然后调用该函数。在这儿,表vtbl中fun()的地址为对象B中的地址。这样就实现了多态。
而对于class A和class B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。通过上面的分析,现在我们来实作一段代码,来描述这个带有虚函数的类的简单模型。
#include
using namespace std;
//将上面“虚函数示例代码”添加在这里
int main(){
void (*fun)(A*);
A *p=new B;
long lVptrAddr;
memcpy(&lVptrAddr,p,4);
memcpy(&fun,reinterpret_cast(lVptrAddr),4);
fun(p);
delete p;
system("pause");
}
输出的结果为3.现在开始一步一步分析。
void (*fun)(A*) 定义了一个函数指针fun,该函数有一个参数,为指向A的指针。这个函数指针待会儿会被用来保存从vtbl中取出的函数地址。
long lVptrAddr; 这个long变量用来保存vptr的值。
memcpy(&lVptrAddr,p,4); 前面说了,p的实例对象中只有vptr指针,所以我们就放心大胆地将p指向的内存地址的前4bytes内容复制到lVptrAddr,复制出来的4bytes内容就是vptr指针,即vbtl的地址。/*指针为4bytes*/
memcpy(&fun,reinterpret_cast(lVptrAddr),4);从vbtl中取出前4bytes内容,即存放在函数指针fun中。注意:由于lVptrAddr不是指针,要先将其转化为指针类型。
fun(p) 调用了刚才取出的函数,也就是B中的fun()。注意:这儿多了一个参数p,其实类成员在函数调用的时候,默认都有一个this指针,这个p就是那个this指针,不写也可以,写成fun()也没问题。
三. Java中的多态
Class A{
public void fun(){
System.out.println("1");
}
}
Class B extends A{
public void fun(){
System.out.println("2");
}
}
public Class C{
public static void main(String[] args){
A a = new B();
a.fun();
}
}
这儿打印的是2.说明Java中默认函数就是虚函数。(虽然Java中没有虚函数的概念,可以这么认为^_^)
但是,如果将fun()声明为私有函数的话,代码如下:
Class A{
private void fun(){
System.out.println("1");
}
public void doFun(){
fun();
}
}
Class B extends A{
private void fun(){
System.out.println("2");
}
}
public Class C{
public static void main(String[] args){
A a = new B();
a.doFun();
}
}
打印结果为1. 而这样的代码在C++中打印结果照样还是多态的效果,即打印结果为2.不知道什么原因,Java的多态实现原理不清楚。