微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

【c++】虚函数和虚函数表多态

多态

静态多态:静态多态是指在编译时实现的多态,比如函数重载,看似是调用一个函数其实是在调用不同的。
动态多态:动态多态是在运行中实现的,当一个父类对象的引用或者指针接收不同的对象(父类对象or子类对象)后,调用相同的函数调用不同的函数

重载、重写、重定义

在这里插入图片描述

简介

函数和纯虚函数的作用

代码示例

class A  
{  
public:  
    virtual void foo()  
    {  
        cout<<"A::foo() is called"<<endl;  
    }  
};  
class B:public A  
{  
public:  
    void foo()  
    {  
        cout<<"B::foo() is called"<<endl;  
    }  
};  
int main(void)  
{  
    A *a = new B();  
    a->foo();   // 在这里,a虽然是指向A的指针,但是被调用函数(foo)却是B的!  
    return 0;  
}

在这里插入图片描述


基类A里面有一个函数表,用数组实现,存放的是子类虚函数指针。

对象的内存布局

class C {
	void fun_a();
	void fun_b();
	int var
}

在这里插入图片描述


其中成员函数放在代码区,为该类的所有对象公有,即不管新建多少个该类的对象,所对应的都是同一个函数存储区的函数。而成员变量则为各个对象所私有,即每新建一个对象都会新建一块内存区用来存储var值。在调用成员函数时,程序会根据类的类型,找到对应代码区所对应的函数并进行调用
如果是普通函数调用,子类继承父类之后会调用父类函数
那么虚函数又是怎么实现的呢?对于

class C {
	void fun_a();
	virtul void fun_b();
	int var
}

其内存布局为:

在这里插入图片描述


函数表指针vptr。这个指针指向一张名为“虚函数表”(vtbl)的表,而表中的数据则为函数指针,存储了虚函数fun_b()具体实现所对应的位置。
注意,普通函数、虚函数、虚函数表都是同一个类的所有对象公有的,只有成员变量和虚函数表指针是每个对象私有的,sizeof的值也只包括vptr和var所占内存的大小(也是个常出现的问题),并且vptr通常会在对象内存的最起始位置。
另外,当类有多个虚函数时,仍然只有一个函数表指针vptr,而此时的虚函数表vtbl中会有多个函数指针,分别指向对应的虚函数实现区域。在重复一遍虚函数实现的过程:通过对象内存中的vptr找到虚函数表vtbl,接着通过vtbl找到对应虚函数的实现区域并进行调用

析构函数和构造函数可以是虚函数吗?

答案是构造函数不能是虚函数,析构函数可以是虚函数且推荐最好设置为虚函数
首先,我们已经知道虚函数的实现则是通过对象内存中的vptr来实现的。而构造函数是用来实例化一个对象的,通俗来讲就是为对象内存中的值做初始化操作。那么在构造函数完成之前,vptr是没有值的,也就无法通过vptr找到作为虚函数的构造函数所在的代码区,所以构造函数只能作为普通函数存放在类所指定的代码区中。
那么为什么析构函数推荐最好设置为虚函数呢?当我们delete()的时候,如果析构函数不是虚函数,那么调用的将会是基类base的析构函数。而当继承的时候,通常派生类会在基类的基础上定义自己的成员,此时我们当时是希望可以调用派生类的析构函数对新定义的成员进行析构。

class A
{
public:
    A(int data) :data(data) {
        p = new int(data);
        cout << "调用A类的构造函数" << endl;
        cout << p << endl;
    }
    ~A() {
        cout << "调用A类的析构函数" << endl;
        delete p;
    }
private:
    int data;
    int* p;
};
class B :public A
{
public:
    //using A::A;//使用基类的构造函数
    B(int data) :A(data) {
        cout << "调用B类的构造函数" << endl;
        p = new int(data);
        cout << p << endl;
    }
    ~B() {
        cout << "调用B类的析构函数" << endl;
        delete p;
    }
private:
    int data;
    int* p;
};
int main(void)
{
    A* a = new B(10);
    delete a;
    return 0;
}

调用结果如下:

在这里插入图片描述


基类指针指向子类,调用基类的析构函数,导致内存泄漏。
改为:virtual ~A()

在这里插入图片描述

原文地址:https://www.jb51.cc/wenti/3280744.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐