PHƯƠNG THỨC ẢO (VIRTUAL FUNCTION)

Một phần của tài liệu Giáo án - Bài giảng: TÀI LIỆU C++ (Trang 122 - 125)

Khi xây dựng các lớp của một chương trình hướng đối tượng để tạo nên một cấu trúc phân cấp hoặc cây phả hệ, người lập trình phải chuẩn bị các hành vi giao tiếp chung của các lớp đó. Hành vi giao tiếp chung sẽ

được dùng để thể hiện cùng một hành vi, nhưng có các hành động khác nhau, đó chính là phương thức ảo. Đây là một phương thức tồn tại để có hiệu lực nhưng khơng có thực trong lớp cơ sở, cịn trong các lớp dẫn

xuất. Như vậy phương thức ảo chỉ được xây dựng khi có một hệ thống cây phả hệ. Phương thức này sẽ được gọi thực hiện từ thực thể của lớp dẫn xuất nhưng mô tả về chúng trong lớp cơ sở.

Chúng ta khai báo phương thức ảo bằng thêm từ khóa virtual ở phía trước. Khi đó các phương thức có cùng tên với phương thức này trong các lớp dẫn xuất cũng là phương thức ảo.

Ví dụ 6.1: 1: //Chương trình 6.1 2: #include <iostream.h> 3: 4: class Base 5: { 6: public:

7: virtual void Display() 8: {

9: cout<<"class Base"<<endl; 10: }

11: }; 12:

13: class Derived : public Base 14: {

15: public:

16: virtual void Display() 17: { 18: cout<<"class Derived"<<endl; 19: } 20: }; 21: 21: void Show(Base *B) 22: {

23: B->Display(); //Con trỏ B chỉ đến phương thức Display() nào (của lớp Base

24 //hoặc lớp Derived) tùy vào lúc chạy chương trình. 25: }

26: int main() 27: {

28: Base *B=new Base;

Giáo trình mơn Lập trình hướng đối tượng Trang 123 30: B->Display(); //Base::Display() 31: D->Display(); //Derived::Display() 32: Show(B); //Base::Display() 33: Show(D); //Derived::Display() 34: return 0; 35: } Chúng ta chạy ví dụ 6.1, kết quả ở hình 6.1 Hình 6.1: Kết quả của ví dụ 6.1

Trong ví dụ 6.1, lớp cơ sở Base có phương thức Display() được khai báo là phương thức ảo. Phương

thức này trong lớp dẫn xuất Derived được định nghĩa lại nhưng cũng là một phương thức ảo. Thật ra, không ra khơng có khai báo virtual cho phương thức Display() của lớp Derived cũng chẳng sao, trình biên dịch vẫn hiểu đó là phương thức ảo. Tuy nhiên, khai báo virtual rõ ràng ở các lớp dẫn xuất làm cho chương trình trong sáng, dễ hiểu hơn. Hai dịng 30 và 31, chúng ta biết chắc phương thức Display() của lớp nào được gọi (của lớp Base hoặc lớp Derived). Nhưng hai dịng 32 và 33, nếu khơng có cơ chế kết nối động, chúng ta đoán rằng việc gọi hàm Show() sẽ luôn luôn kéo theo phương thức Base::Display(). Quả vậy, bỏ đi khai báo

virtual cho phương thức Base::Display(), khi đó dịng lệnh: Show(D);

gọi đến Base::Display() vì đối tượng lớp dẫn xuất cũng là đối tượng lớp cơ sở (nghĩa là tdb tự động

chuyển đổi kiểu: đối tượng D kiểu Derived chuyển thành kiểu Base.

Nhờ khai báo virtual cho phương thức Base::Display() nên sẽ không thực hiện gọi phương thức

Base::Display() một cách cứng nhắc trong hàm Show() mà chuẩn bị một cơ chế mềm dẻo cho việc gọi phương thức Display() tùy thuộc vào sự xác định kiểu của tham số vào lúc chạy chương trình.

Cơ chế đó ra sao? Khi nhận thấy có khai báo virtual trong lớp cơ sở, trình biên dịch sẽ thêm vào mỗi

đối tượng của lớp cơ sở và các lớp dẫn xuất của nó một con trỏ chỉ đến bảng phương thức ảo (virtual

function table). Con trỏ đó có tên là vptr (virtual pointer). Bảng phương thức ảo là nơi chứa các con trỏ chỉ đến đoạn chương trình đã biên dịch ứng với các phương thức ảo. Mỗi lớp có một bảng phương thức ảo. Trình

biên dịch chỉ lập bảng phương thức ảo khi bắt đầu có việc tạo đối tượng của lớp. Đến khi chương trình chạy, phương thức ảo của đối tượng mới được nối kết và thi hành thơng qua con trỏ vptr.

Trong ví dụ 6.1, lệnh gọi hàm: Show(D);

Đối tượng D thuộc lớp Derived tuy bị chuyển đổi kiểu thành một đối tượng thuộc lớp Base nhưng nó

khơng hồn tồn giống một đối tượng của Base chính cống như B. Nếu như con trỏ vptr trong B chỉ đến vị trí trên bảng phương thức ảo ứng với phương thức Base::Display(), thì con trỏ vptr trong D vẫn còn chỉ đến phương thức Derived::Display() cho dù D bị chuyển kiểu thành Base. Đó là lý do tại sao lệnh: Show(D);

gọi đến phương thức Derived::Display(). (adsbygoogle = window.adsbygoogle || []).push({});

Các đặc trưng của phương thức ảo:

Phương thức ảo không thể là các hàm thành viên tĩnh.

Một phương thức ảo có thể được khai báo là friend trong một lớp khác nhưng các hàm friend của lớp thì khơng thể là phương thức ảo.

Khơng cần thiết phải ghi rõ từ khóa virtual khi định nghĩa một phương thức ảo trong lớp dẫn xuất (để cũng chẳng ảnh hưởng gì).

Để sự kết nối động được thực hiện thích hợp cho từng lớp dọc theo cây phả hệ, một khi phương thức

124

trả về và danh sách các tham số. Nếu đối với phương thức ảo ở lớp dẫn xuất, chúng ta lại sơ suất định nghĩa các tham số khác đi một chút thì trình biên dịch sẽ xem đó là phương thức khác. Đây chính là điều kiện để

kết nối động. Ví dụ 6.2: 2: #include <iostream.h> 3: 4: class Base 5: { 6: public:

7: virtual void Print(int A,int B); 8: };

9:

10: class Derived : public Base 11: {

12: public:

13: virtual void Print(int A,double D); 14: };

15:

16: void Base::Print(int A,int B) 17: {

18: cout<<"A="<<A<<",B="<<B<<endl; 19: }

20:

21: void Derived::Print(int A,double D) 22: { 23: cout<<"A="<<A<<",D="<<D<<endl; 24: } 25: 26: void Show(Base *B) 27: { 28: B->Print(3,5); 29: } 30: 31: int main() 32: {

33: Base *B=new Base;

34: Derived *D=new Derived; 35: Show(B); //Base::Print() 36: Show(D); //Base::Print() 37: return 0; 38: } Chúng ta chạy ví dụ 6.2, kết quả ở hình 6.2 Hình 6.2: Kết quả của ví dụ 6.2

Trong ví dụ 6.2, trong lớp cơ sở Base và lớp dẫn xuất Derived đều có phương thức ảo Print(). Nhưng

quan sát kỹ chúng ta, phương thức Print() trong lớp Derived có tham số thứ hai khác kiểu với phương thức Print() trong lớp Base. Vì thế, chúng ta khơng thể chờ đợi lệnh ở dòng 36 sẽ gọi đến phương thức

Derived::Print(int,double). Phương thức Derived::Print(int,double) nằm ngoài đường dây phương thức ảo

nên hàm Show() chỉ luôn gọi đến phương thức Derived::Print(int,int) mà thơi. Do có khai báo virtual đối với phương thức Derived::Print(int,double), chúng ta có thể nói phương thức này sẽ mở đầu cho một đường dây phương thức ảo Print(int,double) mới nếu sau lớp Derived cịn có các lớp dẫn xuất của nó.

Giáo trình mơn Lập trình hướng đối tượng Trang 125

Một phần của tài liệu Giáo án - Bài giảng: TÀI LIỆU C++ (Trang 122 - 125)