多态与虚函数

多态与虚函数

多态

编译时多态

主要是指 函数的重载,编译阶段的多态成为 静态多态

静态绑定称为早绑定。

运行时多态

则和继承、虚函数等概念有关。运行阶段的多态称为 动态多态

动态绑定称为晚绑定。

实现动态绑定的条件:

  1. 赋值兼容的前提
  2. 必须声明为虚函数
  3. 通过基类类型的引用或者指针调用虚函数

虚函数

在函数声明时前面加了 virtual 关键字的 成员函数

virtual关键字只在类定义中的成员函数声明处使用,不能在类外部定义成员函数体时使用。

格式

class 类名
{
  public:
    virtual 类型 函数名(参数表);
};

类型 类名::函数名(参数表)
{
  函数体
};

注意点

  1. 静态成员函数 static内联函数 inline构造函数 不能定义为虚函数。
  2. 派生类重写基类的虚函数实现多态,要求函数名、参数列表及返回值类型要 完全相同
  3. 在基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性,即:虚函数可以被继承
  4. 不要在 构造函数析构函数调用 虚函数。
  5. 最好将 基类的析构函数 声明为虚函数。

通过基类指针实现多态

前提
派生类对象的地址可以赋值给基类指针。

条件
对于通过基类指针调用基类和派生类中都有的同名、同参数表的虚函数的语句。

情况
编译时,系统不确认执行。
运行时,根据基类指针指向的(基类|派生类)对象,来确定执行的虚函数。

#include
using namespace std;

class A
{
  public:
    virtual void print()
    {
      cout << "A::print" << endl;
    }
};

class B: public A
{
  public:
    // 这里不需要在增加 virtual 关键字,自动从基类继承为虚函数
    void print()
    {
      cout << "B::print" << endl;
    }
};

void main()
{
  A a;
  B b;

  A * pa = &a;
  B * pb = &b;
  // 基类指针指向基类对象,调用 a.print() 方法
  pa->print();
  // 派生类指针指向派生类对象,调用 b.print() 方法
  pb->print();
  // 基类指针赋值为派生类指针
  pa = pb;
  // 基类指针指向派生类对象,调用 b.print() 方法
  pa->print();
};

通过基类引用实现多态

通过基类的引用调用虚函数的语句也是多态的。

即通过基类的引用调用基类和派生类中同名、同参数表的虚函数时,若其引用的是一个基类的对象时,则调用的基类的虚函数,若其引用的是一个派生类的对象,则调用的是派生类的虚函数。

#include
using namespace std;

class A
{
  public:
    virtual void print()
    {
      cout << "A::print" << endl;
    }
};

class B: public A
{
  public:
    // 这里不需要在增加 virtual 关键字,自动从基类继承为虚函数
    void print()
    {
      cout << "B::print" << endl;
    }
};

void PrintInfo(A & r)
{
  // 基类引用,同样可以触发虚函数
  r.print();
};

void main()
{
  A a;
  B b;
  PrintInfo(a);
  PrintInfo(b);
};

多态的使用

在普通成员函数(静态成员函数(报错)、构造函数和析构函数(无意义,静态)除外)中调用其它虚成员函数也是允许的,并且是多态的。

#include
using namespace std;

class CBase
{
  public:
    void func1()
    {
      cout << "CBase::func1" << endl;
      // 在普通函数中调用虚函数,保持多态
      func2();
      // 调用普通函数,没有多态
      func3();
    };
    virtual void func2()
    {
      cout << "CBase::func2" << endl;
    };
    void func3()
    {
      cout << "CBase::func3" << endl;
    };
}

class CDerived: public CBase
{
  public:
    void func2()
    {
      cout << "CDerived::func2" << endl;
    };
    void func3()
    {
      cout << "CDerived::func3" << endl;
    };
}

void main()
{
  CDerived d;
  d.func1();
}

// 结果:
// CBase::func1
// CDerived::func2
// CBase::func3

虚析构函数

条件:
如果一个基类指针指向的对象是用new运算符动态生成的派生类对象

情况:
在释放delete该对象所占用的空间时,如果仅调用基类的构造函数,则只会完成该基类析构函数内的空间释放,不会涉及派生类析构函数内的空间释放。

目的:
为了在对象消亡时实现多态,释放派生类空间。

格式

class 类名
{
  public:
    virtual ~ 类名();
}

类名::~类名()
{

};

注意点

  1. 虚析构函数没有返回值类型,没有参数。
  2. 如果一个类的析构函数是虚函数,则由它派生的所有子类的析构函数也是虚析构函数。

例子

#include
using namespace std;

class ABase
{
  ABase()
  {
    cout << "ABase::构造函数" << endl;
  };
  // 声明析构函数为虚析构函数
  virtual ~ABase()
  {
    cout << "ABase::析构函数" << endl;
  };
};

class Derived: public ABase
{
  Derived()
  {
    cout << "Derived::构造函数" << endl;
  };
  ~Derived()
  {
    cout << "Derived::析构函数" << endl;
  };
};

void main()
{
  ABase & pa = new Derived();
  delete pa;
};

// 没有声明为虚析构函数的运行结果,从运行结果中可看出 Derived 派生类没有被析构,内存没有释放
// ABase::构造函数
// Derived::构造函数
// ABase::析构函数

// 声明为虚析构函数的运行结果
// ABase::构造函数
// Derived::构造函数
// Derived::析构函数
// ABase::析构函数

纯虚函数

  1. 纯虚函数的作用相当于一个统一的接口形式,表明在基类的各派生类中应该有这样的操作,然后在各派生类中具体实现。
  2. 纯虚函数时声明在基类中的虚函数,没有具体的定义,而由个派生类根据实际需要给出自己的定义。

格式

class 类名
{
  public:
    virtual 函数类型 函数名(参数表) = 0;
};

与 虚函数 的区别

  1. 纯虚函数没有函数体
  2. 纯虚函数所在的类为抽象类,不能直接进行实例化。

抽象类

含有纯虚函数的类,就叫做抽象类。

注意点

  1. 不允许声明抽象类类型的 对象
  2. 允许声明抽象类类型的 指针引用
  3. 抽象类的派生类中,如果没有给出全部纯虚函数的定义,则派生类继续是抽象类。

虚基类

为了避免产生二义性,C++提供虚基类机制,使得在派生类中,继承同一个间接基类的成员仅保留一个版本。

格式

class 派生类名: virtual 派生方式 基类名
{
  派生体
};

例子

class A {
  public:
    print() {};
};
class B: public A {};
class C: public A {};
class D: public B, public C {};

class VB: virtual public A {};
class VC: virtual public A {};
class VD: public B, public C {};

void main() {
  D d;
  // 产生二义性,这里相当于实例化了两个A基类,print为A基类独有成员函数
  // 此时调用的 print 函数无法明确为哪个A基类中的
  d.print();
  VD vd;
  vd.print(); // 正常运行
};

你可能感兴趣的:(多态与虚函数)