C++多态及其对象模型
一、多态
所谓多态,就是“多种形态”。
在面向对象的方法中一般是这样描述多态的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。多态性的表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。
1.静态多态
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
float Add(float x, float y)
{
return x + y;
}
int main()
{
cout << Add(2, 3) << endl;
cout << Add(2.3f, 3.4f) << endl;
return 0;
}
运行结果如下: 静态多态是通过函数重载实现的。由函数重载和运算符重载形成的多态性属于静态多态,要求编译器在程序编译时就知道调用函数的全部信息。静态多态性又称编译时的多态性。静态多态性的函数调用速度快,效率高,但缺乏灵活性,在程序运行前就已经决定了执行的函数和方法。
2.动态多态
C++中虚函数的主要作用就是实现动态多态。简单说父类的指针/引用调用重写的虚函数,当父类指针/引用指向父类对象时调用的是父类的虚函数,指向子类对象时调用的是子类的虚函数。
虚函数:类的成员函数前面加上virtual关键字,则这个成员函数称为虚函数。
虚函数的重写:当在子类中定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(覆盖)了父类的这个虚函数。
可以看到:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中的不同类的对象,从而调用其中的同名函数。
注意:由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数做出不同的响应。动态多态性的特点是:不在编译时确定是哪个函数,而是在程序运行过程中动态的确定操作所针对的对象,它又称运行时多态性。
虚析构函数:
先看如下代码
程序并没有执行派生类的析构函数。如果希望能够执行派生类的析构函数,可以将基类的析构函数声明为虚函数。
结论:
不加virtual关键字,不构成多态,此时与类型有关,指针p是父类类型,所以只调用父类析构函数。
加上virtual关键字,构成多态,此时与对象有关,会先析构子类对象,再自动调用父类析构函数。
构造函数不能被定义为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上把函数与类对象绑定。
静态成员函数不能是虚函数。因为静态成员函数的特点是不受限制于某个对象。
内联函数不能是虚函数。因为内联函数不能在运行中动态确定位置。
构造函数中不能调用虚函数。虚函数的地址是存在对象里的,调用构造函数之前对象还没初始化,及没有虚函数,更不存在调用可言。
二、多态的对象模型——单继承和多继承
1.单继承
多态中单继承概念:一个子类继承一个父类,子类中对父类的虚函数进行重写,子类继承父类的虚表之后,在此虚表中所对应虚函数存储位置的关系。
用以下例子进行分析:
由上图可知,子类Derive继承父类Base,重写了父类中的虚函数func1(),所以在子类的虚表中可以看到,首先存放的是,子类重写的虚函数func1(),然后是父类的虚函数func2(),子类的虚函数func3(),最后是子类的虚函数func()。在虚表的最后存放了一个0作为结束标志。
2.多继承
多态中多继承的概念:一个子类继承2个或2个以上的父类,子类中对父类的虚函数进行重写,子类会分别继承父类的虚表,同时分别继承的虚表中虚函数的存放位置关系如下图所示:
由上图可知:一个子类Derive继承了两个父类,Base1和Base2. 同时继承了两个虚表,在子类对象中通过内存及打印函数得到,子类中有两个虚表,继承父类Base1的虚表中存放了子类Derive重写的虚函数func1(),继承父类Base1中的虚函数func2(),以及子类Derive中的虚函数func3();另一个继承父类Base2的虚表中存放了子类Derive重写的虚函数func1(),继承父类Base1中的虚函数func2()。同时,虚表的结束标志是0.
三、多态的对象模型--菱形继承和菱形虚拟继承
1.菱形继承
菱形继承是几个类的继承关系呈菱形状。为此,我们举例解释:
有类A,B,C,D。其中B,C继承A,D继承B,C。继承关系图如下:
具体代码如下:
#include <iostream>
using namespace std;
class A
{
public:
virtual void f1()
{
cout<<"A::f1()"<<endl;
}
virtual void f2()
{
cout<<"A::f2()"<<endl;
}
public:
int _a;
};
class B:public A
{
public:
virtual void f1()
{
cout<<"B::f1()"<<endl;
}
virtual void f3()
{
cout<<"B::f3()"<<endl;
}
public:
int _b;
};
class C:public A
{
public:
virtual void f1()
{
cout<<"C::f1()"<<endl;
}
virtual void f4()
{
cout<<"C::f4()"<<endl;
}
public:
int _c;
};
class D:public B,public C
{
public:
virtual void f1()
{
cout<<"D::f1()"<<endl;
}
virtual void f5()
{
cout<<"D::f5()"<<endl;
}
public:
int _d;
};
//打印虚函数表
typedef void(*V_FUNC)();
void PrintVtable(int* vtable)
{
printf("vtable:%p\n",vtable);
int** pvtable=(int**)vtable;
for(size_t i=0; pvtable[i]!=0;i++)
{
printf("vtable[%u]:0x%p->",i,pvtable[i]);
V_FUNC f=(V_FUNC)pvtable[i];
f();
}
cout<<"------------------------------------\n";
}
void test()
{
D d;
d.B::_a=5;
d.C::_a=6;
d._b=1;
d._c=2;
d._d=3;
PrintVtable(*(int**)&d);
PrintVtable(*(int**)((char*)&d+sizeof(B)));
}
int main()
{
test();
return 0;
}
创建一个D类的对象d,下图为查看d对象中存储的成员变量情况,以及虚表指向:
解析图:
小结:
在普通的菱形继承中,处于最先的D类型的对象d,它继承了B,C,并且B,C中分别保存了一份来自继承A的变量;除此之外,B,C还存了虚表指针,通过它可以找到虚表中存的虚函数地址,最后,d对象还存放了自己定义的变量和继承B,C自己定义的变量。
菱形继承中,创建一个D类对象d,d中存放了B类的虚表指针及其成员_b、继承A类的_a、C类的虚表指针及其成员_c,继承A类的_a,以及自身的_d。
因为D类中对函数func1()进行了重写,所以在B类的虚表中存放了D类的虚函数func1(),A类的虚函数func2(),B类的虚函数func3(),和D类的虚函数func5();
在C类的虚表中存放了D类的虚函数func1(),A类的虚函数func2(),C类的虚函数func4()。
2.菱形虚拟继承
菱形虚拟继承就是在普通菱形继承的前提下加了虚继承(本例是,B,C虚继承A)。
创建一个D类的对象d,下图为查看d对象中存储的成员变量情况,以及虚表指向:
代码如下:
#include <iostream>
using namespace std;
class A
{
public:
virtual void f1()
{
cout<<"A::f1()"<<endl;
}
virtual void f2()
{
cout<<"A::f2()"<<endl;
}
public:
int _a;
};
class B:virtual public A
{
public:
virtual void f1()
{
cout<<"B::f1()"<<endl;
}
virtual void f3()
{
cout<<"B::f3()"<<endl;
}
public:
int _b;
};
class C: virtual public A
{
public:
virtual void f1()
{
cout<<"C::f1()"<<endl;
}
virtual void f4()
{
cout<<"C::f4()"<<endl;
}
public:
int _c;
};
class D:public B,public C
{
public:
virtual void f1()
{
cout<<"D::f1()"<<endl;
}
virtual void f5()
{
cout<<"D::f5()"<<endl;
}
public:
int _d;
};
//打印虚函数表
typedef void(*V_FUNC)();
void PrintVtable(int* vtable)
{
printf("vtable:%p\n",vtable);
int** pvtable=(int**)vtable;
for(size_t i=0; pvtable[i]!=0;i++)
{
printf("vtable[%u]:0x%p->",i,pvtable[i]);
V_FUNC f=(V_FUNC)pvtable[i];
f();
}
cout<<"------------------------------------\n";
}
void test()
{
D d;
d.B::_a=5;
d.C::_a=6;
d._b=1;
d._c=2;
d._d=3;
PrintVtable(*(int**)&d);
PrintVtable(*(int**)((char*)&d+sizeof(B)-sizeof(A)));
PrintVtable(*(int**)((char*)&d+sizeof(D)-sizeof(A)));
}
int main()
{
test();
return 0;
}
解析图:
小结:
菱形虚拟继承与菱形继承的区别在于,B,C继承A的公共成员a,既不存储在B里,也不存储在C里,而是存储在一块公共的部分,而会将B,C相对于这个变量的偏移地址(这里的偏移量地址叫做虚基表)存在B,C里。所以说,对象d里的B,C里存放了虚表指针,虚基表指针,自己的变量。