C++多态(虚函数、静态链接、动态链接、虚函数表、虚表指针)

今天来看看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(虚函数表)。每个有虚函数的类都有一个虚函数表,这个类的所有对象共用一个虚函数表,虚函数表实际上就是虚函数的指针数组。

扫描二维码关注公众号,回复: 12698530 查看本文章

例如下面这段代码:

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、关于虚函数表和虚函数指针

猜你喜欢

转载自blog.csdn.net/weixin_45146520/article/details/109537291