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

Một phần của tài liệu Giáo trình lập trình hướng đối tượng (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;

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 hoàn toà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().

Các đặc trưng ca phương thc 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 (để (adsbygoogle = window.adsbygoogle || []).push({});

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 nào đó đã được xác định là ảo, từ lớp cơ sởđến các lớp dẫn xuất đều phải định nghĩa thống nhất về tên, kiểu

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ó.

125

Một phần của tài liệu Giáo trình lập trình hướng đối tượng (Trang 122 - 125)