今天来看看C++的多态,发现一直不清晰。
什么是多态
多态字面意思就是多种状态,在有继承的类中会出现。下面来看一个例子:
静态多态
#include<iostream>
using namespace std;
class A {
public:
A():i(10){
}
void f() {
cout<< "A::f()"<<i<<endl;}
int i;
};
class B : public A{
public:
B():j(20) {
}
void f() {
cout << "B::f()" << j <<endl;}
int j;
};
class C : public A{
public:
C():q(30) {
}
void f() {
cout << "C::f()" << q <<endl;}
int q;
};
int main()
{
A a;
B b;
C c;
A *p = &b;
A *q = &c;
p->f();
q->f();
return 0;
}
这种情况下程序会输出什么呢?
我们会发现结果全都是 A::f()10
;
这是因为在编译时就已经确定了以基类也就是A的函数来当做输出的函数,所以用基类指针指向子类对象还是会调用基类的函数,这就是静态链接,也叫静态多态。
再看下一个例子:
动态多态
还是同样的程序,我们对基类的f函数加上virtual,将其定义为虚函数。
#include<iostream>
using namespace std;
class A {
public:
A():i(10){
}
virtual void f() {
cout<< "A::f()"<<i<<endl;}
int i;
};
class B : public A{
public:
B():j(20) {
}
void f() {
cout << "B::f()" << j <<endl;}
int j;
};
class C : public A{
public:
C():q(30) {
}
void f() {
cout << "C::f()" << q <<endl;}
int q;
};
int main()
{
A a;
B b;
C c;
A *p = &b;
A *q = &c;
p->f();
q->f();
return 0;
}
运行结果为:
B::f()20
C::f()30
这时候指针指向的就是子类的函数了。这就是多态中的动态绑定,也叫动态多态。
虚函数
前面写到用到虚函数后,实现了动态多态,我们接下来分析下在内存中发生了什么。
如果我们输出一个正常的类的大小,例:
class A{
A(){
}
void print(){
cout<<"1"<<endl;}
}
int main(){
cout<<sizeof(A)<<endl;
}
再把print函数变成虚函数后输出它的大小:
class A{
A(){
}
virtual void print(){
cout<<"1"<<endl;}
}
int main(){
cout<<sizeof(A)<<endl;
}
我们会发现虚函数的类要比普通函数的类大一点,这是为什么呢?
因为每个拥有虚函数的类的对象前面都会加上一个指针,指向一个存放虚函数的表格,我们称它为vtable,这个表是所有对象共享的。
vtable
上面说到,为了实现多态,c++使用了动态绑定方法,这个方法的核心就是vtable(虚函数表)。每个有虚函数的类都有一个虚函数表,这个类的所有对象共用一个虚函数表,虚函数表实际上就是虚函数的指针数组。
例如下面这段代码:
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
它的虚表就是以下情况:
虚表指针
因为虚函数表是属于类的,所有对象公用一个虚函数表。那每一个对象怎么知道自己的虚函数表是哪个呢?*每个对象通过一个虚表指针 __vptr 指向它的虚函数表。
动态绑定
下面就根据一个例子看C++如何利用虚函数表和虚表指针实现动态绑定。
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
class B : public A {
public:
virtual void vfunc1();
void func1();
private:
int m_data3;
};
class C: public B {
public:
virtual void vfunc2();
void func2();
private:
int m_data1, m_data4;
};
根据继承关系可以得到如下结果:
虚函数与纯虚函数
class A{
virtual void A(){
cout<<"A"<<endl;}
virtual void B()=0;
}
上面就是一个虚函数和纯虚函数共同存在的例子。它们的区别就在于虚函数提供了函数的实现,所以子类可以选择重写或直接继承这个函数,而纯虚函数则要求子类必须实现这个函数。
抽象类
拥有纯虚函数的类是抽象类,抽象类就相当于一个行为规范的定义,只能当基类。比如说我们希望子类一定要完成一些动作,就将这些动作写成纯虚函数。
需要注意的是,继承了抽象类但没全部实现纯虚函数的,仍然是抽象类。
巨人的肩膀:
1、关于虚函数表和虚函数指针