虚拟函数

虚拟关键字是函数说明符,会根据原理选择动态的运行时间,并在基本或派生类别中找出恰当函数。架构中不能有虚拟函数,它只能用于改变函数声明

虚拟函数,与一般函数一样,都需要 可执行体。当调用时,语义与其他函数一样

虚拟函数在派生类里可能会遭到拒绝,选择函数定义的种类需要虚拟函数的动态调用(在运行时间里),典型情况是,当基本类别包含虚拟函数时,派生类都含有函数里特有的版本。

指针指向基本类别里可以标明基本分类对象或者派生类对象,函数成员选择的调用会在运行时间内进行,会依靠类型对象执行,并不是指标的类型,如果没有派生类成员,数据分类的虚拟函数就是默认值。

无论是否用虚拟关键字描述,析构函数永远是虚拟的。

在MT5_Tetris.mq5例子中虚拟函数的使用,虚拟函数 Draw 的基本类别CTetrisShape,定义在包含文件 MT5_TetrisShape.mqh.中。

//+------------------------------------------------------------------+
class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  };

进一步说,对于每一个分类,该函数都会依照下一代分类的特性来执行,例如,第一个模型CTetrisShape1有自己的执行函数Draw() :

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- 绘画形状
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- 水平线
         for(i=0; i<4; i++)
           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
           }
        }
      else
        {
         //--- 垂直线
         for(i=0; i<4; i++)
           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+i*SHAPE_SIZE);
           }
        }
     }
  }

方形台由CTetrisShape6 分类描述,并实现Draw() 语法:

class CTetrisShape6 : public CTetrisShape
  {
public:
   //--- 绘画形状
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      for(i=0; i<2; i++)
        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
        }
      for(i=2; i<4; i++)
        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+(i-2)*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+SHAPE_SIZE);
        }
     }
  };

根据分类,建立对象归属,在派生类里调用虚拟函数。

void CTetrisField::NewShape()
  {
//--- 创建7个随机形状中的一个
   int nshape=rand()%7;
   switch(nshape)
     {
      case 0: m_shape=new CTetrisShape1; break;
      case 1: m_shape=new CTetrisShape2; break;
      case 2: m_shape=new CTetrisShape3; break;
      case 3: m_shape=new CTetrisShape4; break;
      case 4: m_shape=new CTetrisShape5; break;
      case 5: m_shape=new CTetrisShape6; break;
      case 6: m_shape=new CTetrisShape7; break;
     }
//--- 绘画
   m_shape.Draw();
//---
  }

修饰符 'override' #

'override' 修饰符表示声明的函数必须重写父类的函数类。通过这个函数类您可以避免重写错误,例如它可以让您避免意外修改函数类签名。假设,'func' 函数类定义在基本类。函数类会接受int变量为自变量:

class CFoo
  {
   void virtual func(int x) const { }
  };

下一步,函数类重写在子类:

class CBar : public CFoo
  {
   void func(short x) { }
  };

然而,自变量类型错误的从int 变为short。实际上,这不是类函数重写,而是类函数重载。根据重载函数定义算法,在某些情况下编译器可以选择在基类中定义的函数类来替代重写函数类。

为了避免这种错误,您应该明确添加 'override' 修饰符到您想重写的函数类。

class CBar : public CFoo
  {
   void func(short x) override { }
  };

如果在重写过程中更改了函数类签名,编译器将无法再父类中找到相同签名的函数类,并将返回一个编译错误:

'CBar::func' 函数类是通过'override' 标识符声明但不会重写任何基本  的类函数

修饰符 'final' #

'final' 修饰符是相反的 ― 它禁止函数类在子类中重写。如果函数类的实现非常充分并得到完全完成,则以 'final' 修饰符声明这个函数类以确保其以后不被修改。

class CFoo
  {
   void virtual func(int x) final { }
  };
 
class CBar : public CFoo
  {
   void func(int) { }
  };
 

如果您像上面示例一样试图以 'final' 修饰符重写函数类,编译器将返回一个错误:

声明为'final'的'CFoo::func' 函数类不能通过 'CBar::func'重写
请见 'CFoo::func' 声明

另见

标准程序库