C++ 多态
C++中多态是怎么使用的?
用基类指针或引用 存储/绑定 派生类 对象地址/对象,如果访问的接口是虚函数,则访问的是虚表中对应函数,而不只是基类函数。
可以看出,这种操作能够实现一个接口多种实现,所以我们将它称为多态。
当给类写了虚函数,类就会增加一个虚指针(虚函数表指针)成员,其地址为类的首地址。并且编译器会生成一个虚函数表,用于存储类的所有虚函数的指针。
如图:
代码测试:测试是否为上图结构
#include <iostream>
using namespace std;
class Base
{
public:
virtual void fun(){
cout << "function from Base" << endl;
}
void test()
{
void (*f)() = (void(*)())(*(int*)(*(int*)this));
f();
}
};
int main(){
Base b;
b.test();
}
运行结果:
多态的实现原理:
早期绑定的概念:
C++编译器在编译的时候,要确定每个对象调用非虚函数的地址,并且将它绑定下来。而虚函数是在程序运行时通过虚指针访问虚表再调用对应的虚函数。
我们以以下例子分析虚函数与普通函数的不同:
//测试代码:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void v_fun(){
cout << "virtual function from Base" << endl;
}
void fun(){
cout << "function from Base" << endl;
}
};
class Derived
:public Base
{
public:
virtual void v_fun(){
cout << "virtual function from Derived" << endl;
}
void fun(){
cout << "function from Derived" << endl;
}
};
int main(){
Derived d;
Base* bptr = &d;
bptr->v_fun();
bptr->fun();
}
运行结果:
分析:
晚绑定:
当编译器编译到 bptr->v_fun()时,由于调用的是虚函数,编译器并不会绑定函数地址,而是程序运行到这个地方时,访问bptr所指向的虚指针再通过虚指针访问虚表中对应的函数。由于虚表是Derived对象的虚表,所以访问的是Derived对象的v_fun函数。
早绑定
当编译器编译到 bptr->fun()时,由于调用的是普通函数,所以编译器会直接将其绑定至对象对应函数地址,注意:bptr作为基类指针,不通过虚表则只能访问到基类的成员函数,所以此时绑定的是基类函数的地址。
虚表的继承:
当派生类继承一个含有虚表的基类时,派生类会先继承基类的虚表,然后改写虚表中函数指针,如果有自己增加的虚函数,则在虚表中添加。但是需要我们注意的是,基类指针只能访问基类已经实现的方法,所以就算派生类中添加了虚函数,通过基类指针也是访问不到的。那为什么还要添加到虚表呢?因为派生类也可以作为基类被继承。