Виртуальные функции
Ключевое слово virtual служит спецификатором функции, который обеспечивает механизм для динамического выбора на этапе выполнения подходящей функции-члена среди функций базового и производного классов, структуры не могут иметь виртуальных функций. Оно может применяться для изменения объявлений только функций-членов.
Виртуальная функция, как и обычная функция, должна иметь исполняемое тело. При вызове семантика ее точно такая же, как и у остальных функций.
Виртуальная функция может замещаться в производном классе. Выбор того, какое определение функции вызвать для виртуальной функции, происходит динамически (на этапе выполнения). Типичный случай – когда базовый класс содержит виртуальную функцию, а производные классы имеют свои версии этой функции.
Указатель на базовый класс может указывать либо на объект базового класса, либо на объект производного класса. Выбор вызываемой функции-члена будет произведен на этапе выполнения и будет зависеть от типа объекта, а не от типа указателя. При отсутствии члена производного типа по умолчанию используется виртуальная функция базового класса.
Деструкторы всегда являются виртуальными, независимо от того, объявлены они с ключевым слово virtual или нет.
Внимание: Не рекомендуется вызывать виртуальные методы из конструкторов и деструкторов, потому что поведение программы в этих случаях неопределено.
Рассмотрим использование виртуальных функций на примере программы Tetris.mq5. Во включаемом файле TetisShape.mqh определен базовый класс CTetrisShape с виртуальной функцией Draw (рисовать).
//+------------------------------------------------------------------+
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();
//---
} |