这里简单介绍下什么是多态,多态的构成条件,多态原理以及多态的对象模型。在介绍多态之前,先简单的介绍下什么是虚函数。
虚函数
类的成员函数前面加virtual关键字,则这个成员函数称为虚函数。
注:1. 除静态成员函数 2. 内联函数不能定义为虚函数
虚函数重写:
当在子类的定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也称覆盖)了父类的这个虚函数。
纯虚函数
在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
纯虚函数强制重写虚函数。
多态
1.定义
多态就是多形态。
静态多态就是重载,因为是在编译期决议确定的,所以称为静态多态。
动态多态就是通过继承重写基类的虚函数实现多态,因为是在运行时决议的,所以称为冬天多态。
当使用基类的指针或引用调用重写的虚函数时,当指向父类调用的就是父类的虚函数,当指向子类调用的就是子类的虚函数。
注:普通调用与类型有关,多态调用与对象有关。
2.条件
1.虚函数重写 2.父类的指针和引用(指向父类调用父类,指向子类调用子类)
eg:
class Base
{
public :
virtual void func1()
{
cout<<"Base::func1" <<endl;
}
virtual void func2()
{
cout<<"Base::func2" <<endl;
}
public:
int a ;
};
class Derive :public Base
{
public :
virtual void func1()
{
cout<<"Derive::func1" <<endl;
}
virtual void func3()
{
cout<<"Derive::func3" <<endl;
}
virtual void func4()
{
cout<<"Derive::func4" <<endl;
}
public:
int b ;
};
这是一种运行期多态,即父类指针唯有在程序运行时才能知道所指的真正类型是什么。这种运行期决议,是通过虚函数表来实现的。
注:
1). 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。
2). 在基类中定义类虚函数,在该函数中虚函数始终保持虚函数的特性。
3).四个默认成员函数,除了析构,都不要定义为虚函数。析构函建议定义为虚函数(能保证正确调用对应虚函数)。
why? 另外析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里是因为编译器做了特殊处理)因为父类的指针可能指向父类对象也可能指向子类对象,为了让指向谁调用谁的析构,所用定义为多态,否则会造成内存泄露。
4). 静态成员还是那户不能定义为虚函数。
多态对象模型
使用指针访问虚表
eg:
class Base
{
public :
virtual void func1()
{
cout<<"Base::func1" <<endl;
}
virtual void func2()
{
cout<<"Base::func2" <<endl;
}
public:
int a ;
};
typedef void(*V_FUNC)();
void PrintVTable(int** vtable)
{
cout<<"===================================="<<endl;
printf("虚函数表:%p\n", vtable);
for (size_t i = 0; vtable[i] != 0; ++i)
{
printf("vfunc[%d]:%p->", i, vtable[i]);
V_FUNC f = (V_FUNC)vtable[i];
f();
}
cout<<"===================================="<<endl;
}
void Test()
{
Base b;
PrintVTable((int**)(*((int**)&b)));
}
分析:
单继承对象模型
写一个类继承Base
eg:
class Derive :public Base
{
public :
virtual void func1()
{
cout<<"Derive::func1" <<endl;
}
virtual void func3()
{
cout<<"Derive::func3" <<endl;
}
virtual void func4()
{
cout<<"Derive::func4" <<endl;
}
public:
int _b ;
};
分析:
在C++对象模型中,对于一般继承(这个一般是相对于虚拟继承而言),若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数(注意子类与父类拥有各自的一个虚函数表);若子类并无overwrite父类虚函数,而是声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后(在vs中无法通过监视看到扩充的结果,不过我们通过取地址的方法可以做到,子类新的虚函数确实在父类子物体的虚函数表末端)。
多继承对象模型(非菱形继承)
eg:
class Base1
{
public :
virtual void func1()
{
cout<<"Base1::func1" <<endl;
}
virtual void func2()
{
cout<<"Base1::func2" <<endl;
}
private :
int b1 ;
};
class Base2
{
public :
virtual void func1()
{
cout<<"Base2::func1" <<endl;
}
virtual void func2()
{
cout<<"Base2::func2" <<endl;
}
private :
int b2 ;
};
class Derive : public Base1, public Base2
{
public :
virtual void func1()
{
cout<<"Derive::func1" <<endl;
}
virtual void func3()
{
cout<<"Derive::func3" <<endl;
}
private :
int d1 ;
};
typedef void(*V_FUNC)();
void PrintVTable(int** vtable)
{
cout<<"===================================="<<endl;
printf("虚函数表:%p\n", vtable);
for (size_t i = 0; vtable[i] != 0; ++i)
{
printf("vfunc[%d]:%p->", i, vtable[i]);
V_FUNC f = (V_FUNC)vtable[i];
f();
}
cout<<"===================================="<<endl;
}
void Test()
{
Derive d;
PrintVTable((int**)(*((int**)&d)));
PrintVTable((int**)(*(int**)((char*)&d+sizeof(Base1))));
}
分析:
单继承中(一般继承),子类会扩展父类的虚函数表。在多继承中,子类的虚函数被放在声明的第一个基类的虚函数表中。
注:
1.C++中,编译时多态主要是通过函数重载和运算符重载实现的。运行时多态性主要通过虚函数重写实现。
2.C++动态决议的虚拟机制中,使用的vtable就是一个用来保存虚成员函数的地址的函数指针数组。
3. 同类型的对象虚表相同(共享同一块),拷贝时只拷贝成员变量不拷贝虚表。