zoukankan      html  css  js  c++  java
  • C++虚函数表与虚析构函数

    1.静态联编和动态联编
    联编:将源代码中的函数调用解释为要执行函数代码。

    静态联编:编译时能确定唯一函数。
    在C中,每个函数名都能确定唯一的函数代码。
    在C++中,因为有函数重载,编译器须根据函数名,参数才能确定唯一的函数代码。

    动态联编:编译时不能确定调用的函数代码,运行时才能。
    C++中因为多态的存在,有时编译器不知道用户将选择哪种类型的对象,因此无法确定调用的唯一性,只有在运行时才能确定。

    2.虚成员函数,指针或引用,动态联编
    指针或引用才能展现类的多态性。
    当类中的方法声明为virtual时,使用指针或引用调用该方法,就是动态联编。
    若是普通方法,则为静态联编。
    示例如下:

    class Test
    {
    public:
        virtual show()
        {
            std::cout<<"Test::show()"<<std::endl;
        }
    };
    class SubTest:public Test
    {
    public:
        virtual show()
        {
            std::cout<<"SubTest::show()"<<std::endl;
        }
    };
    int main()
    {
        SubTest subTest;
        Test * p = &subTest;//指向子类的指针
        Test & a = subTest;//子类的引用
        Test * p2 = new Test;//指向父类的指针
        p->show();
        a.show();
        p2->show();
        return 0;
    }

    程序没有释放内存,我们将在后面析构函数的时候,完善该程序。

    3.动态联编使用原则
    动态联编,需要跟踪基类指针或引用指向的实际对象类型,因此效率低于静态联编
    1)当类不会用作基类时,成员函数不要声明为virtual
    2)当成员函数不重新定义基类的方法,成员函数不要声明为virtual

    4.关于虚函数
    1)父类成员函数若声明为virtual,则子类中也是虚的,若要重新定义该方法,可显式加上virtual关键字,也可不加,编译器编译时会自动加上。
    2)使用指向对象的引用或指针来调用虚方法,将使用具体对象类型定义的方法,而不一定是引用或指针类型定义的方法。
    SubTest subTest;
    Test * p = &subTest;//指向子类的指针
    p->show();//将调用SubTest对象定义的show()方法

    5.虚函数的工作原理
    当类中存在虚函数时,编译器默认会给对象添加一个隐藏成员。该成员为一个指向虚函数表(virtual function table,vtbl)的指针。
    虚函数表是一个保存了虚函数地址的数组。编译器会检查类中所有的虚函数,依次将每个虚函数的地址,存入虚函数表。

    class Test
    {
    public:
        virtual show()
        {
            std::cout<<"Test::show()"<<std::endl;
        }
    private:
        int a;
    };
    class SubTest:public Test
    {
    public:
        virtual show()
        {
            std::cout<<"SubTest::show()"<<std::endl;
        }
    };

    内存结构图如下所示:

    可以看出,父类和子类有独立的虚函数表,且虚函数表中虚函数指针也指向各自的虚函数地址,
    若子类没有覆盖父类中的show方法,则虚函数指针show_ptr指向的虚函数show()的地址是一样的,均指向父类show()函数地址。虚函数表的存在和动态联编,就是多态的原理。

    6.虚析构函数
    1)构造函数是特殊的,是没有虚函数的概念的。
    构造函数是不继承的,创建子类对象时,将调用子类的构造函数,子类的构造函数将自动调用父类的构造函数。

    2)析构函数应是虚函数,除非类不用做基类。
    我们看下面的代码:
    Test *p = new SubTest;
    delete p;
    p=NULL;

    由虚函数表,我们知道,若析构函数不声明为virtual,则调用的将是Test类的析构函数,而没有调用SubTest类的析构函数,此时造成了内存泄露。
    所以析构函数必须声明为虚函数,调用的将是子类SubTest的析构函数,
    我们还需要知道的一点是,子类析构函数,一定会调用父类析构函数,释放父类对象,则内存安全释放。
    我们第一个例子的完整的示例代码如下:

    class Test
    {
    public:
        virtual show()
        {
            std::cout<<"Test::show()"<<std::endl;
        }
        virtual ~Test(){}
    };
    class SubTest:public Test
    {
    public:
        virtual show()
        {
            std::cout<<"SubTest::show()"<<std::endl;
        }
    };
    int main()
    {
        SubTest subTest;
        Test * p = &subTest;//指向子类的指针
        Test & a = subTest;//子类的引用
        Test * p2 = new Test;//指向父类的指针
        p->show();
        a.show();
        p2->show();
        delete p;
        p=NULL;
        delete p2;
        p2=NULL;
        return 0;
    }

    参考资料:《C++ Primer.Plus》 pp.490-507
                       http://www.imooc.com/video/9199

  • 相关阅读:
    【模板时间】◆模板·III◆ 单调子序列
    【学时总结】◆学时·VII◆ 高维DP
    【例题收藏】◇例题·IV◇ Wooden Sticks
    【赛时总结】◇赛时·VI◇ Atcoder ABC-104
    【例题收藏】◇例题·III◇ 木と整数 / Integers on a Tree
    【学时总结】◆学时·VI◆ SPLAY伸展树
    【模板时间】◆模板·II◆ 树链剖分
    【赛时总结】◇赛时·V◇ Codeforces Round #486 Div3
    【例题收藏】◇例题·II◇ Berland and the Shortest Paths
    【例题收藏】◇例题·I◇ Snuke's Subway Trip
  • 原文地址:https://www.cnblogs.com/shijingjing07/p/5559989.html
Copyright © 2011-2022 走看看