Hàm ảo
Từ khóa virtual
là một bộ chỉ định hàm, cung cấp cơ chế để chọn động tại thời điểm chạy một hàm thành viên phù hợp trong số các hàm của lớp cơ sở và lớp dẫn xuất. Cấu trúc không thể có hàm ảo. Nó chỉ có thể được sử dụng để thay đổi khai báo cho các hàm thành viên.
Hàm ảo, giống như hàm thông thường, phải có một thân thực thi. Khi được gọi, ngữ nghĩa của nó giống như các hàm khác.
Hàm ảo có thể được ghi đè trong lớp dẫn xuất. Việc lựa chọn định nghĩa hàm nào sẽ được gọi cho hàm ảo được thực hiện động (tại thời điểm chạy). Một trường hợp điển hình là khi lớp cơ sở chứa một hàm ảo, và các lớp dẫn xuất có phiên bản riêng của hàm này.
Con trỏ tới lớp cơ sở có thể trỏ đến đối tượng của lớp cơ sở hoặc đối tượng của lớp dẫn xuất. Việc lựa chọn hàm thành viên để gọi sẽ được thực hiện tại thời điểm chạy và phụ thuộc vào kiểu của đối tượng, không phải kiểu của con trỏ. Nếu không có thành viên của kiểu dẫn xuất, hàm ảo của lớp cơ sở được sử dụng theo mặc định.
Hàm hủy luôn là ảo, bất kể chúng được khai báo với từ khóa virtual
hay không.
Hãy xem xét việc sử dụng hàm ảo qua ví dụ của MT5_Tetris.mq5. Lớp cơ sở CTetrisShape với hàm ảo Draw được định nghĩa trong tệp đính kèm 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[]);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Tiếp theo, đối với mỗi lớp dẫn xuất, hàm này được triển khai phù hợp với đặc điểm của lớp con. Ví dụ, hình dạng đầu tiên CTetrisShape1 có triển khai riêng của hàm Draw():
class CTetrisShape1 : `public` CTetrisShape
{
public:
//--- vẽ hình dạng
virtual void Draw()
{
int i;
string name;
//---
if(m_turn==0 || m_turn==2)
{
//--- ngang
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
{
//--- dọc
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);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Hình vuông được mô tả bởi lớp CTetrisShape6 và có triển khai riêng của phương thức Draw():
class CTetrisShape6 : `public` CTetrisShape
{
public:
//--- Vẽ hình dạng
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);
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Tùy thuộc vào lớp mà đối tượng được tạo thuộc về, nó sẽ gọi hàm ảo của lớp dẫn xuất này hoặc lớp dẫn xuất kia.
void CTetrisField::NewShape()
{
//--- tạo ngẫu nhiên một trong 7 hình dạng có thể
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;
}
//--- vẽ
m_shape.Draw();
//---
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Bộ sửa đổi 'override'
Bộ sửa đổi override
có nghĩa là hàm được khai báo phải ghi đè phương thức của lớp cha. Việc sử dụng phương thức này cho phép tránh lỗi ghi đè, ví dụ, nó giúp tránh việc vô tình sửa đổi chữ ký phương thức. Giả sử phương thức func
được định nghĩa trong lớp cơ sở. Phương thức này chấp nhận một biến kiểu int làm đối số:
class CFoo
{
void virtual func(int x) const { }
};
2
3
4
Tiếp theo, phương thức được ghi đè trong lớp con:
class CBar : `public` CFoo
{
void func(short x) { }
};
2
3
4
Tuy nhiên, kiểu đối số bị thay đổi nhầm từ int thành short. Thực tế, đây không phải là ghi đè phương thức, mà là nạp chồng phương thức. Hành động theo thuật toán xác định hàm nạp chồng, trình biên dịch có thể trong một số tình huống chọn phương thức được định nghĩa trong lớp cơ sở thay vì phương thức được ghi đè.
Để tránh những lỗi như vậy, bạn nên thêm rõ ràng bộ sửa đổi override
vào phương thức bạn muốn ghi đè.
class CBar : `public` CFoo
{
void func(short x) override { }
};
2
3
4
Nếu chữ ký phương thức bị thay đổi trong quá trình ghi đè, trình biên dịch sẽ không thể tìm thấy phương thức với cùng chữ ký trong lớp cha, và nó sẽ trả về lỗi biên dịch:
'CBar::func' method is declared with 'override' specifier but does not override any base class method
Bộ sửa đổi 'final'
Bộ sửa đổi final
thực hiện điều ngược lại — nó cấm ghi đè phương thức trong các lớp con. Nếu một triển khai phương thức là đủ và hoàn chỉnh, hãy khai báo phương thức này với bộ sửa đổi final
để đảm bảo rằng nó sẽ không bị sửa đổi sau này.
class CFoo
{
void virtual func(int x) final { }
};
class CBar : `public` CFoo
{
void func(int) { }
};
2
3
4
5
6
7
8
9
Nếu bạn cố gắng ghi đè một phương thức với bộ sửa đổi final
như trong ví dụ trên, trình biên dịch sẽ trả về lỗi:
'CFoo::func' method declared as 'final' cannot be overridden by 'CBar::func'
see declaration of 'CFoo::func'
2
Xem thêm