Phương thức ảo và tương ứng bội
3.1. Cách định nghĩa phương thức ảo
Giả sử A là lớp cơ sở, các lớp B, C, D dẫn xuất (trực tiếp hoặc dán tiếp) từ A. Giả sử trong 4
lớp trên đều có các phương thức trùng dòng tiêu đề (trùng kiểu, trùng tên, trùng các đối). Để
định nghĩa các phương thức này là các phương thức ảo, ta chỉ cần:
+ Hoặc thêm từ khoá virtual vào dòng tiêu đề của phương thức bên trong định nghĩa lớp cơ
sở A.
+ Hoặc thêm từ khoá virtual vào dòng tiêu đề bên trong định nghĩa của tất cả các lớp A, B,
C và D.
Ví dụ:
Cách 1:
class A
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp A” ;
};
} ;
class B : public A
{
...
void hien_thi()
{
cout << “\n Đây là lớp B” ;
};
} ;
class C : public B
{
...
void hien_thi()
{
cout << “\n Đây là lớp C” ;
};
} ;
class D : public A
{
...
329 330
void hien_thi()
{
cout << “\n Đây là lớp D” ;
};
} ;
Cách 2:
class A
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp A” ;
};
} ;
class B : public A
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp B” ;
};
} ;
class C : public B
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp C” ;
};
} ;
class D : public A
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp D” ;
};
} ;
Chú ý: Từ khoá virtual không được đặt bên ngoài định nghĩa lớp. Ví dụ nếu viết như sau là
sai (CTBD sẽ báo lỗi).
class A
331 332
{
...
virtual void hien_thi() ;
} ;
virtual void hien_thi() // Sai
{
cout << “\n Đây là lớp A” ;
};
Cần sửa lại như sau:
class A
{
...
virtual void hien_thi() ;
} ;
void hien_thi() // Đúng
{
cout << “\n Đây là lớp A” ;
};
3.2. Quy tắc gọi phương thức ảo
Để có sự so sánh với phương thức tĩnh, ta nhắc lại quy tắc gọi phương thức tĩnh nêu trong
§
1.
3.2.1. Quy tắc gọi phương thức tĩnh
Lời gọi tới phương thức tĩnh bao giờ cũng xác định rõ phương thức nào (trong số các
phương thức trùng tên của các lớp có quan hệ thừa kế) được gọi:
1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào, thì phương thức của lớp đó sẽ được
gọi.
2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp nào, thì phương thức của lớp đó sẽ được
gọi bất kể con trỏ chứa địa chỉ của đối tượng nào.
3.2.2. Quy tắc gọi phương thức ảo
Phương thức ảo chỉ khác phương thức tĩnh khi được gọi từ một con trỏ (trường hợp 2 nêu
trong mục 3.2.1). Lời gọi tới phương thức ảo từ một con trỏ chưa cho biết rõ phương thức nào
(trong số các phương thức ảo trùng tên của các lớp có quan hệ thừa kế) sẽ được gọi. Điều này
phụ thuộc vào đối tượng cụ thể mà con trỏ đang trỏ tới: Con trỏ đang trỏ tới đối tượng của lớp
nào thì phương thức của lớp đó sẽ được gọi.
Ví dụ A, B, C và D là các lớp đã định nghĩa trong 3.1. Ta khai báo một con trỏ kiểu A và 4
đối tượng:
A *p ; // p là con trỏ kiểu A
A a ; // a là biến đối tượng kiểu A
B b ; // b là biến đối tượng kiểu B
C c ; // c là biến đối tượng kiểu C
D d ; // d là biến đối tượng kiểu D
Xét lời gọi tới các phương thức ảo hien_thi sau:
p = &a; // p trỏ tới đối tượng a của lớp A
p->hien_thi() ; // Gọi tới A::hien_thi()
p = &b; // p trỏ tới đối tượng b của lớp B
p->hien_thi() ; // Gọi tới B::hien_thi()
p = &c; // p trỏ tới đối tượng c của lớp C
p->hien_thi() ; // Gọi tới C::hien_thi()
p = &d; // p trỏ tới đối tượng d của lớp D
p->hien_thi() ; // Gọi tới D::hien_thi()
3.3. Tương ứng bội
Chúng ta nhận thấy cùng một câu lệnh
p->hien_thi();
tương ứng với nhiều phương thức khác nhau. Đây chính là tương ứng bội. Khả năng này rõ
ràng cho phép xử lý nhiều đối tượng khác nhau, nhiều công việc, thậm chí nhiều thuật toán
khác nhau theo cùng một cách thức, cùng một lược đồ. Điều này sẽ được minh hoạ trong các
mục tiếp theo.
3.4. Liên kết động
Có thể so sánh sự khác nhau giữ phương thức tĩnh và phương thức ảo trên khía cạnh liên kết
một lời gọi với một phương thức. Trở lại ví dụ trong 3.2:
A *p ; // p là con trỏ kiểu A
A a ; // a là biến đối tượng kiểu A
B b ; // b là biến đối tượng kiểu B
C c ; // c là biến đối tượng kiểu C
D d ; // d là biến đối tượng kiểu D
Nếu hien_thi() là các phương thức tĩnh, thì dù p chứa địa chỉ của các đối tượng a, b, c hay d,
thì lời gọi:
p->hien_thi() ;
luôn luôn gọi tới phương thức A::hien_thi()
Như vậy một lời gọi (xuất phát từ con trỏ) tới phương thức tĩnh luôn luôn liên kết với một
phương thức cố định và sự liên kết này xác định trong quá trình biên dịch chương trình.
Cũng với lời gọi:
p->hien_thi() ;
như trên, nhưng nếu hien_thi() là các phương thức ảo, thì lời gọi này không liên kết cứng với
một phương thức cụ thể nào. Phương thức mà nó liên kết (gọi tới) còn chưa xác định trong giai
đoạn dịch chương trình. Lời gọi này sẽ:
+ liên kết với A::hien_thi() , nếu p chứa địa chỉ đối tượng lớp A
333 334
+ liên kết với B::hien_thi() , nếu p chứa địa chỉ đối tượng lớp B
+ liên kết với C::hien_thi() , nếu p chứa địa chỉ đối tượng lớp C
+ liên kết với D::hien_thi() , nếu p chứa địa chỉ đối tượng lớp D
Như vậy một lời gọi (xuất phát từ con trỏ) tới phương thức ảo không liên kết với một
phương thức cố định, mà tuỳ thuộc vào nội dung con trỏ. Đó là sự liên kết động và phương
thức được liên kết (được gọi) thay đổi mỗi khi có sự thay đổi nội dung con trỏ trong quá trình
chạy chương trình.
3.5. Quy tắc gán địa chỉ đối tượng cho con trỏ lớp cơ sở
+ Như đã nói trong
§
1, C++ cho phép gán địa chỉ đối tượng của một lớp dẫn xuất cho con
trỏ của lớp cơ sở. Như vậy các phép gán sau (xem 3.2) là đúng:
A *p ; // p là con trỏ kiểu A
A a ; // a là biến đối tượng kiểu A
B b ; // b là biến đối tượng kiểu B
C c ; // c là biến đối tượng kiểu C
D d ; // d là biến đối tượng kiểu D
p = &a; // p và a cùng lớp A
p = &b; // p là con trỏ lớp cơ sở, b là đối tượng lớp dẫn xuất
p = &c; // p là con trỏ lớp cơ sở, c là đối tượng lớp dẫn xuất
p = &d; // p là con trỏ lớp cơ sở, d là đối tượng lớp dẫn xuất
+ Tuy nhiên cần chú ý là: Không cho phép gán địa chỉ đối tượng của lớp cở sở cho con trỏ
của lớp dẫn xuất. Như vậy ví dụ sau là sai:
B *q ;
A a ;
q = &a;
Sai vì: Gán địa chỉ đối tượng của lớp cơ sở A cho con trỏ của lớp dẫn xuất B
3.6. Ví dụ
Ta sửa chương trình trong
§
1 bằng cách định nghĩa các phương thức xuat() là ảo. Khi đó
bốn câu lệnh:
hien(&a);
hien(&b);
hien(&c);
hien(&d);
trong hàm main (của chương trình dưới đây) sẽ lần lượt gọi tới 4 phương thức khác nhau:
A::xuat()
B::xuat()
C::xuat()
D::xuat()
//CT6-01B
// Phuong thuc ảo và tương ứng bội
#include <conio.h>
335 336