C++Fuck iOS EveryDay

多态,虚函数,纯虚函数,虚函数表

2018-04-03  本文已影响39人  曹小恒

多态性:一个接口,多种方法.程序在运行时才确定调用的函数,是 oop 的核心概念.

重写有两种,一种是重写虚函数(体现多态),另一种就是重写成员函数(并没有体现)

和重写相对的另一个概念是重载(overloading),指的是多个重名的函数他们的参数列表不同(个数,类型),编译器通过函数的调用参数列表来决定调用的

#include<iostream>  
using namespace std;  
  
class A  
{  
public:  
    void foo()  
    {  
        printf("1\n");  
    }  
    virtual void fun()  
    {  
        printf("2\n");  
    }  
};  
class B : public A  
{  
public:  
    void foo()  
    {  
        printf("3\n");  
    }  
    void fun()  
    {  
        printf("4\n");  
    }  
};  
int main(void)  
{  
    A a;  
    B b;  
    A *p = &a;  
    p->foo();  
    p->fun();  
    p = &b;  
    p->foo();  
    p->fun();  
    return 0;  
}  

输出为

1 2 1 4

对于输出1 2是没有问题的,在第三和第四个输出的时候,因为基类指针指向了子类,而foo()函数没有被虚拟化,所以,这是一个早绑定,只能调用基类的同名函数,fun()是一个基类中的虚函数,所以可以被晚绑定,调用子类中的函数,从而实现了一个借口,多个函数的多态性.

纯虚函数:在基类中定义的虚函数,没有定义,派生类需要定义自己的实现方法.

virtual void function() = 0;

派生类中必须进行重写以实现多态性. 含有纯虚函数的类成为抽象类,不能生成对象.

虚函数表(vtable): 虚函数是通过虚函数表来实现的,这个表主要是一个类虚函数的地址表,这张表解决了继承和多态的问题,保证了真实反映和使用实际的函数.这个表在一个对象实例的最前面,我们可以通过变量虚函数表的函数指针,调用相应的函数.

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

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }
};
Base b;

这个对象实例b的结构如下:

图片.png
  1. 一对一继承(子类没有覆盖重写虚函数,这样在实际中没有意义,仅为对比)
    如果子类也有自己的虚函数,并继承父类的虚函数表,则其虚函数表的结构为:


    图片.png

    1)虚函数按照声明顺序放在表中
    2)父类的虚函数放在子类的前面

  2. 一对一继承(有虚函数被覆盖)
    父子两个对象的表分别如下:


    图片.png

则最后子类的虚函数表将为:


图片.png

1)覆盖的虚函数,父类位置被子类顶替
2)其余顺序不变
因此,如果有

Base *b = new Derive();
b->f();

该父类对象指向的地址是子类的地址,对象b将调用覆盖后的 Derive::f().
这就是多态实现的原理.

  1. 多重继承(无覆盖)
    如果子类对父类的虚函数没有覆盖:


    图片.png

    那么子类实例中的虚函数表是这样子的:


    图片.png
    1)每个父类有自己的虚表
    2)子类的虚函数在第一个父类之后
    3)父类顺序是按照声明顺序来的

    当每次设计到这些虚函数的时候,需要对应到相应的虚函数表(根据基类的定义),比如:

Base2 *ptr = new d();
ptr->f()  //调用第二个表的第一个函数(Base2 的 f())
  1. 多重继承(有虚函数覆盖)


    图片.png

    所有相应的同名虚函数都要被覆盖:


    图片.png

安全性

上一篇下一篇

猜你喜欢

热点阅读