多态的实现条件
多态(与对象有关 到对象的虚表中找到)=动态联编+虚函数重写
动态联编(运行时决议):指针/引用+虚函数
静态联编(编译时决议)与类型有关,像函数重载和通过对象名调用虚函数
1.定义一个父类的指针,如果这个指针指向父类,调用父类的虚函数,指针指向子类,调用子类的虚函数,这样可以使一个函数有不同的实现,即多态的体现。
2.根据模板的不同实例,实现不同类型的使用。
用一个例代码更好地理解
class A
{
public:
virtual void f1()
{
cout<<"A::f1()"<<endl;
}
void f2()
{
cout<<"A::f2()"<<endl;
}
void f3()
{
cout<<"A::f3()"<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = NULL;
//p->f1();(运行不通过,此处动态联编,寻找虚表)
p->f2();//静态联编,不是虚函数,f2存在于代码段中,直接可以调用
//p->f3();//(静态联编,this指针解引用后成空)
return 0;
}
虚析构函数
这里我们先来清楚一个问题,为什么最好把父类的析构函数定义成虚函数呢?
#include<iostream>
using namespace std;
class A
{
public:
virtual~A()
{
cout<<"~A()"<<endl;
}
};
class B:public A
{
public:
~B()
{
cout<<"~B()"<<endl;
}
};
int main()
{
A* p=new B;
delete p;
return 0;
}
现象:给父类加上virtual后,会先析构子类的析构函数,再析构父类的析构函数。
分析:把父类的析构函数声明成虚函数,这将使所有派生类的析构函数自动成为虚函数。这样,如果想要显式地通过delete删除一个对象,delete的操作对象用了指向派生类对象的基类指针,则会调用基类的析构函数。
探索虚函数表
通过一块连续内存来存储虚函数的地址。
正如上图中监视显示,虚函数表_vfptr中存放着虚函数fun1和fun2的地址。
单继承有虚函数但是没有重写虚函数
通过监视窗口可以看到,父类中有一个虚函数,父类的实例化对象中有一个虚表指针和一个成员变量,然后子类中有一个函数,但不是虚函数,但为什么子类的成员变量里会有一个虚表指针呢?因为它是从父类里继承而来的。需要说明的是,这个函数指针,指向的内容是父类的虚函数。
单继承有虚函数有重写虚函数
class A
{
public:
virtual void show()
{
cout<<"A show()"<<endl;
}
public:
int _a;
};
class B:public A
{
public:
void show()
{
cout<<"B show()"<<endl;
}
void show1()
{
cout<<"B show()"<<endl;
}
public:
int _b;
};
int main()
{
A a;
B b;
return 0;
}
通过和上面的继承虚函数相比,子类的虚函数表中的函数地址已经变成了子类的函数了。
探索单继承对象模型
class Base
{
public:
virtual void fun1()
{
cout<<"Base::fun1"<<endl;
}
virtual void fun2()
{
cout<<"Base::fun2"<<endl;
}
private:
int a;
};
class Derive:public Base
{
public:
virtual void fun1()
{
cout<<"Derive::fun1"<<endl;
}
virtual void fun3()
{
cout<<"Derive::fun3"<<endl;
}
virtual void fun4()
{
cout<<"Derive::fun4"<<endl;
}
private:
int b;
};
void Test()
{
Base b1;
Derive d1;
}
对于Derive类来说,我们预想的是,它的虚表里会有子类重写父类的fun1,父类的fun2,子类的fun3和fun4.
但是,通过监视窗口可知只有fun1()和fun2()。这里是因为编译器的问题所以没有显示出fun3和fun4。
所以接下来我们需要实现一个可以打印虚函数表的函数。
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
cout<<"虚表地址"<<VTable<<endl;
for(int i=0;VTable[i]!=0;++i)
{
printf("低%d个虚函数地址:0X%x->",i,VTable[i]);
FUNC f=(FUNC) VTable[i];//转为指针类型
f();
}
cout<<endl;
}
void Test()
{
Base b1;
Derive d1;
int* VTable1=(int*)(*(int*)&b1);
int* VTable2=(int*)(*(int*)&d1);
PrintVTable(VTable1);
PrintVTable(VTable2);//显式打印
}
函数的实现:
这里还有一点不好理解,就是传参问题
探索多继承的内存布局
学习了单继承对象模型,接下来我们看看多继承,同样,通过代码调试观察监视从而了解多继承的内存布局。
class Base1
{
public:
virtual void fun1()
{
cout<<"Base::fun1"<<endl;
}
virtual void fun2()
{
cout<<"Base::fun2"<<endl;
}
private:
int b1;
};
class Base2
{
public:
virtual void fun1()
{
cout<<"Base::fun1"<<endl;
}
virtual void fun2()
{
cout<<"Base::fun2"<<endl;
}
private:
int b2;
};
class Derive:public Base1,public Base2
{
public:
virtual void fun1()
{
cout<<"Derive::fun1"<<endl;
}
virtual void fun3()
{
cout<<"Derive::fun3"<<endl;
}
private:
int d1;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
cout<<"虚表地址"<<VTable<<endl;
for(int i=0;VTable[i]!=0;++i)
{
printf("低%d个虚函数地址:0X%x->",i,VTable[i]);
FUNC f=(FUNC) VTable[i];//转为指针类型
f();
}
cout<<endl;
}
void Test()
{
Derive d1;
int* VTable=(int*)(*(int*)&d1);
PrintVTable(VTable);
VTable = (int*)(*((int*)&d1+sizeof(Base1)/4));
PrintVTable(VTable);//显式打印
}
int main()
{
Test();
return 0;
}
多继承中有两个虚函数表,分别是Base1和Base2的虚函数表,我们猜想子类里的fun1()会覆盖父类Base1和Base2中的fun1(),但是子类里的fun3()会放在Base1里还是Base2里还是自己开辟一个虚函数表呢?
接下来,我们通过监视窗口看看:
结果证实了子类里的fun1()会覆盖父类Base1和Base2中的fun1(),但是,同单继承中出现的问题一样,编译器依旧无法在监视窗口中显示出fun3()的情况。
所以,靠自己吧!
通过上图的打印结果可知,fun3()存在于Base1的虚函数表中。当涉及多继承时,子类的虚函数会存放于先继承的那个类的虚函数表里。