13.多态、虚函数

多态与虚函数:

什么是虚函数:

用virtual关键字声明的函数都是虚函数。虚函数存在的唯一目的,就是为了实现多态(动态绑定/运行时绑定)。

virtual 关键字只在类定义中的成员函数声明处使用,不能在类外部写成员函数体时使用。所有派生类中具有覆盖关系的同名函数都将自动成为虚函数。

静态成员函数不能是虚函数。

再说简单点:有virtual声明的函数都是虚函数。如果没有virtual,那么派生类中的同名函数会把基类中的同名函数隐藏了。如果有,那么派生类的同名函数(同参同返回)会在虚函数表中将基类的同名函数覆盖掉。

什么是多态:

        多态性可以简单概括为“一个接口,多种行为”。

动态绑定(运行阶段)是多态的基础。

基类指针或引用,指向一系列的派生类对象, 调用派生类对象的同名覆盖方法(也就是那个与基类虚函数同名同参同返回的函数),指针指向谁,就会调用谁的方法。

多态分为两种:

        (1)编译时多态(也叫静态的多态):主要通过函数的重载和模板来实现。

        (2)运行时多态(也叫动态的多态):主要通过虚函数来实现。

覆盖:

       基类的某个成员函数为虚函数,派生类又定义一个成员函数,函数名、形参、返回类型都与基类的成员函数相同。那么就会用派生类的函数覆盖掉基类的虚函数


说一下多态是如何实现的:

在代码编译阶段产生一张虚函数表vftable。运行的时候,会载入内存,加载到数据段。一个类的虚函数表中列出了该类的全部虚函数地址。

如果成员里有虚函数,在成员变量里只会多一个虚函数指针(对象的前4个字节),指向虚函数表vftable()存放虚函数地址。虚函数的个数只会影响表的大小。不会影响对象的大小。

基类有虚函数,派生类如果有同名同参数列表同返回值的函数,派生类的函数会自动变成虚函数,在虚函数表中派生类会覆盖掉原有的函数。

举个栗子:

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        int i;
        virtual void func() {}
        virtual void func2() {}
    };
    class B : public A
    {
        int j;
        void func() {}
    };
    int main()
    {
        cout << sizeof(A) << ", " << sizeof(B);
        return 0;
    }

 设 pa 的类型是 A*,则 pa->func() 这条语句的执行过程如下:

1) 取出 pa 指针所指位置的前 4 个字节,也就是虚函数指针。如果 pa 指向的是类 A 的对象,则这个地址就是类 A 的虚函数表的地址;类 B 同

2) 根据虚函数指针找到虚函数表,在其中查找要调用的虚函数的地址。

如果 pa 指向的是类 A 的对象,自然就会在类 A 的虚函数表中查出 A::func 的地址;类 B 同

类 B 没有自己的 func2 函数,因此在类 B 的虚函数表中保存的是 A::func2 的地址,这样,即便 pa 指向类 B 的对象,pa->func2();这条语句在执行过程中也能在类 B 的虚函数表中找到 A::func2 的地址。

3) 根据找到的虚函数的地址调用虚函数。

由以上过程可以看出,只要是通过基类指针或基类引用调用虚函数的语句,就一定是多态的,也一定会执行上面的查表过程,哪怕这个虚函数仅在基类中有,在派生类中没有。


动态绑定与静态绑定:

绑定就是函数调用。

在使用的时候,用一个基类的指针指向了一个派生类的对象,如果调用这个虚函数,会先找虚函数指针,再找虚函数表,再再找虚函数地址。而这个绑定过程就是动态绑定(运行时期)。

如果你不用指针调用,而是用对象本身调用函数,不论是否是虚函数,都是静态绑定(编译时期)。

用指针调用如果是虚函数,指针识别的就是运行时期的类型;如果调用的是一般的函数,指针识别就是在编译时期。

  • 没有virtual -> 静态绑定
  • 有 virtual 用引用或指针  ->   动态绑定
  • 有 virtual 但用对象调用->   动态绑定

纯虚函数:

一般情况下,基类是不希望定义对象的。基类只是为了将共有的属性统一起来。

为了实现这一目的:在基类提供的一个虚函数,为所有派生类提供统一的虚函数接口,具体实现让派生类自己去重写的。

virtual void show() = 0;  // 在虚函数后面加   =  0    就是纯虚函数,不用去实现。

纯虚函数实际上是不存在的,引入纯虚函数就是为了便于实现多态。

拥有纯虚函数的类叫做抽象类。抽象类不能实例化。一般基类都应该实现为抽象类。

那么问题来了:

1、基类在没有更多方法的时候,把谁实现成纯虚函数呢?-------->  析构函数

首先明确一个函数想要成为虚函数     1、它得有地址;2、得依赖对象

1)构造函数能不能是虚函数?

构造函数不依赖对象,构造函数执行完才有对象,有对象才有虚函数指针,所以不能是虚函数。

2)static成员方法能不能是虚函数?  virtual static

静态函数也不依赖对象,可以直接使用类名调用,也不能是虚函数。

3)inline函数能不能是虚函数?  =>  virtual inline  

内联函数直接在程序内展开,没有地址,无法往虚函数表放。也不能是虚函数。

4)析构函数能不能实现成虚函数?

析构函数依赖对象,有地址。可以写成虚函数

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章