C++多态性
概念:
多态,按照字面意思就是多种形态。在面向对象语言中,接口的多种不同实现方式即为多态。允许你将父类对象指向一个或更多其他的子类对象,赋值后,父类对象可以根据当前赋值给他的子对象的特征,从而以不同方式运转。
静态多态:
在系统编译期间就可以确定程序执行到这里将要执行哪个函数;
比如 函数的重载 以及 泛型编程。
动态多态:
利用虚函数实现了运行时的多态,也就是说在系统编译的时候并不知道程序将要调用哪一个函数,只有在运行到这里的时候才能确定接下来会跳转到哪一个函数的栈帧。
实现:
C++的多态性是通过虚函数来实现的。虚函数允许子类重新定义成员函数,子类重新定义父类的做法叫做覆盖或重写(只有重写虚函数才算体现了C++的多态性)。
例:
单继承模型:
class A
{
public:
virtual void fun() {
cout << "A::fun()" << endl;
};
void foo() {
cout << "A::foo()" << endl;
};
int _a;
};
class B : public A
{
public:
virtual void fun() {
cout << "B::fun()" << endl;
};
void foo() {
cout << "B::foo()" << endl;
};
int _b;
};
void test()
{
A a1;
B b1;
A *p = &a1;
p->fun();
p->foo();
p = &b1;
p->fun();
p->foo();
}
第一个p->foo(),p->fun()输出很好理解,因为其本身是基类指针,指向基类对象,所以调用的都是基类本身的函数。输出”A::foo()”,”A::fun()”。
第二个p->foo(),p->fun()则是基类指针指向派生类对象,p->foo()由于指针是一个基类指针,指向固定偏移量的函数,因此此时指向的就只能是基类的foo()函数。p->fun()指针是一个基类指针,指向一个虚函数,每个虚函数都有一个虚函数列表,此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址。所以输出“A::foo(),B::fun()”;
此时B类的对象模型为
单继承中虚表的情况已经在http://blog.csdn.net/hudazhe/article/details/78156314中讨论过。
多继承中对象模型:
class A
{
public:
A()
:_a(1)
{}
virtual void fun1() {
cout << "A::fun1()" << endl;
};
virtual void fun2(){
cout << "A::fun2()" << endl;
}
int _a;
};
class B
{
public:
B()
:_b(2)
{}
virtual void fun3() {
cout << "B::fun3()" << endl;
};
virtual void fun4() {
cout << "B::fun4()" << endl;
}
int _b;
};
class C :public A, public B
{
public:
C()
:_c(3)
{}
virtual void fun1() {
cout << "C::fun1()" << endl;
}
virtual void fun3() {
cout << "C::fun3()" << endl;
}
virtual void fun5(){
cout << "C::fun5()" << endl;
}
int _c;
};
调用监视窗口可以发现
C类对象中的虚表有两个。
分别打印出两个虚表中的函数:
可以看出此时C类对象的对象模型变为了:
在多继承中,从不同基类中继承的虚函数,将生成不同的虚函数。而派生类中没有构成重写的虚函数的地址将放在第一张虚表的后面。
菱形继承模型
菱形继承便是单继承与多继承的结合。
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
virtual void fun2()
{
cout << "A::fun2()" << endl;
}
int _a;
};
class B1 :public A
{
public:
virtual void fun1()
{
cout << "B1::fun1()" << endl;
}
virtual void fun3()
{
cout << "B1::fun3()" << endl;
}
int _b1;
};
class B2 :public A
{
public:
virtual void fun2()
{
cout << "B2::fun2()" << endl;
}
virtual void fun4()
{
cout << "B2::fun4()" << endl;
}
int _b2;
};
class C :public B1, public B2
{
public:
virtual void fun3()
{
cout << "C::fun3()" << endl;
}
virtual void fun5()
{
cout << "C::fun5()" << endl;
}
int _c;
};
void test()
{
C c1;
c1.B1::_a = 1;
c1.B2::_a = 2;
c1._b1 = 3;
c1._b2 = 4;
c1._c = 5;
}
调用监视窗口后发现也存在两张虚表
打印两张虚表中虚函数的地址
此时的对象模型为:
可以发现菱形继承的对象模型是与多继承非常类似的。
虚继承中的对象模型:
class A
{
public:
A()
:_a(1)
{}
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
virtual void fun2()
{
cout << "A::fun2()" << endl;
}
int _a;
};
class B :virtual public A
{
public:
B()
:_b(2)
{}
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void fun3()
{
cout << "B::fun3()" << endl;
}
int _b;
};
void test()
{
B b1;
}
因为在虚继承中,对象模型中会多出一个偏移量表格,所以此时调用监视窗口会发现。
因为偏移量表格只会有一张,而通过内存2监视查看每个地址所指向的内容发现,此时的偏移量表格并不是放在顶端,而是放在第一张虚表的后面。尝试打印每个地址中函数的地址会发现
此时的对象模型为:
在虚拟继承中偏移量表格不是放在顶部,而是放在第一张偏移量表格的下面。
派生类中,未能构成重写的虚函数,将生成一张新的虚函数表,放在对象的顶部。
构成重写的虚函数,以及从基类中继承的虚函数将生成一张虚表,放在类静态成员的后面。
菱形虚拟继承的对象模型:
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
virtual void fun2()
{
cout << "A::fun2()" << endl;
}
int _a;
};
class B1 :virtual public A
{
public:
virtual void fun1()
{
cout << "B1::fun1()" << endl;
}
virtual void fun3()
{
cout << "B1::fun3()" << endl;
}
int _b1;
};
class B2 :virtual public A
{
public:
virtual void fun2()
{
cout << "B2::fun2()" << endl;
}
virtual void fun4()
{
cout << "B2::fun4()" << endl;
}
int _b2;
};
class C :public B1, public B2
{
public:
virtual void fun3()
{
cout << "C::fun3()" << endl;
}
virtual void fun5()
{
cout << "C::fun5()" << endl;
}
int _c;
};
void test()
{
C c1;
c1._a = 1;
c1._b1 = 3;
c1._b2 = 4;
c1._c = 5;
}
调用监视窗口发现,C类对象中,有两个偏移量表格,三个虚表指针。
此时打印出每个虚表指针所指向地址的虚函数地址。
此时可以画出C类对象的对象模型:
菱形虚拟继承的对象模型可以看做是多继承与虚继承的结合。
因为B1,B2是从基类中虚拟继承得到的所以他们将分别生成两个偏移量表格。
因为继承中基类成员先构造
先构造C类对象,形成一个虚表。&A::fun1() &A::fun2();
B1虚继承C,fun1()构成重写,形成两个虚表
[ &B1::fun3() ],
[ &B1::fun1(),&A::fun2() ];
B2虚继承C,fun2()构成重写,形成两个虚表
[ &B2::fun4() ],
[ &A::fun1(),&B::fun2() ];
多继承中,基类的初始化顺序按照声明次序进行,所以先执行public B1:
因为虚继承中,派生类中未构成重写的虚函数应放在基类中第一个虚表的后面,并且fun3()构成重写,所以此时的虚表变为:
[ &C1::fun3(),&C1::fun5() ]
[ &B1::fun1(),&A::fun2() ]
之后执行public B2:
因为派生类中未构成重写的虚函数已经放在B1的虚表中,此时只需要考虑函数的重写。因为fun4()没有重写,所以fun4()虚表不变,此时
[ &B1::fun1(),&A::fun2() ]
[ &A::fun1(),&B::fun2() ]
相对基类构成了重写,此时两张虚表变为一张
[ &B1::fun1(),&B2::fun2() ]
所以最终的虚表应该为
[ &C1::fun3(),&C1::fun5() ]
[ &B2::fun4() ]
[ &B1::fun1(),&B2::fun2() ]
流程图为
至此大部分继承方式的对象模型已经全部解决。