C++多态,虚函数作用及底层实现原理

来源:互联网 发布:plc编程功能块 编辑:程序博客网 时间:2024/06/05 13:33

简述C++虚函数作用及底层实现原理

1.foreword

C++是面向对象程序设计,其包括3项特点:
(1)数据抽象:接口和实现分离
(2)继承:父类和子类
(3)多态:动态绑定

本文讨论多态。

当父类希望子类重新定义某些函数时,用virtual关键字声明为虚函数。

当我们使用一个基类类型的引用或者指针,调用一个虚函数时就引发动态绑定/多态的发生。函数运行版本由传入的实参类型决定。可以用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。

父类指针好像有“多种类型”。这是一种泛型技术,试图用不变的代码实现可变的算法,比如模板技术,虚函数技术等。

引用或者指针的静态类型与动态类型不同这一事实,是C++支持多态的核心。

上述这句话选自《C++ Primer》,颇有玄门正宗之感。复杂的技术根植于底层的原理,所以搞清原理方可深入浅出。

当我们使用基类的引用、指针去调用基类中定义的一个虚函数时,并不知道该函数真正作用的对象是什么类型,可能是一个基类对象,也可能是一个派生类对象,这一切直到运行时才可知道。所以多态又称运行时绑定/动态绑定。

对于非虚函数的调用在编译时即可确定,哪个对象,哪个方法,地址是确定的。

2.虚函数底层实现原理

当你记住上面的概念时,它仅是概念。但当真正了解底层原理后,发现其中妙不可言。

如果让你来实现虚函数的功能?你会怎么实现?

复杂的代码?高深的原理?其实C++中实现的非常接地气。

虚函数是通过虚函数表和虚函数指针来实现的。

该表一般位于某类型的对象实例在内存中的最开始的位置。

单类继承

class Base {public:    virtual void f() { cout << "Base::f()" << endl; }    virtual void g() { cout << "Base::g()" << endl; }    virtual void h() { cout << "Base::h()" << endl; }};

父类对象其在内存中布局示意如下:
这里写图片描述

虚函数表的尾部为虚函数表的结束结点。

再定义一个子类,此时并不覆盖父类的虚函数:

class Derived :public Base {public:    virtual void f1() { cout << "Derived::f1()" << endl; }    virtual void g1() { cout << "Derived::g1()" << endl; }    virtual void h1() { cout << "Derived::g1()" << endl; }};

这里写图片描述

此时可以得知:
(1)虚函数按照声明顺序放在表中;
(2)父类的虚函数,排在子类虚函数之前。

当我们把子类中的函数覆盖时:

class Derived :public Base {public:    // f() override    void f() { cout << "Derived::f1()" << endl; }    virtual void g1() { cout << "Derived::g1()" << endl; }    virtual void h1() { cout << "Derived::g1()" << endl; }};

这里写图片描述

此时可以得知:
(1)子类覆盖的虚函数,放在原来父类该虚函数的位置;所以当父类指针指向该子类对象时,调用方法就会调用子类重载的方法;
(2)没有被覆盖的虚函数依旧。

多类继承

当发生多类继承时:

这里写图片描述

虚函数表内存排列示意如下:

这里写图片描述

此时可以得知:
(1)每个继承的父类,都有自己的虚函数表;
(2)子类虚函数,放在第一个声明顺序的父类的表中。

当子类虚函数重写时,此时类的继承为:

这里写图片描述

子类将f( )函数重写,则内存中的排列为:

这里写图片描述

此时我们可以得知:
(1)三个父类虚函数表中的f( )函数被替换为子类函数指针。因此当我们用任一静态类型的父类指针来指向子类时,调用f( )时就调用的子类的f( )。


参考资料:
[1] http://www.cnblogs.com/webary/p/4731457.html

原创粉丝点击