Đa hình (Polymorphsim) trong C++ (Phần 1)

Đa hình là khái niệm luôn đi kèm với kế thừa. Do tính kế thừa, một lớp có thể sử dụng lại các phương thức của lớp khác. Tuy nhiên, nếu cần thiết, lớp dẫn xuất cũng có thể định nghĩa lại một số phương thức của lớp cơ sở. Đó là sự nạp chồng phương thức trong kế thừa. Nhờ sự nạp chồng phương thức này, ta chỉ cần gọi tên phương thức bị nạp chồng từ đối tượng mà không cần quan tâm đó là đối tượng của lớp nào. Chương trình sẽ tự động kiểm tra xem đối tượng là thuộc kiểu lớp cơ sở hay thuộc lớp dẫn xuất, sau đó sẽ gọi phương thức tương ứng với lớp đó. Đó là tính đa hình. 



Đặt vấn đề 
Sự kế thừa trong C++ cho phép có sự tương ứng giữa lớp cơ sở và các lớp dẫn xuất trong sơ đồ thừa kế:  


  • Một con trỏ có kiểu lớp cơ sở luôn có thể trỏ đến địa chỉ của một đối tượng của lớp dẫn xuất. 
  • Tuy nhiên, khi thực hiện lời gọi một phương thức của lớp, trình biên dịch sẽ quan tâm đến kiểu của con trỏ chứ không phải đối tượng mà con trỏ đang trỏ tới: phương thức của lớp mà con trỏ có kiểu được gọi chứ không phải phương thức của đối tượng mà con trỏ đang trỏ tới được gọi.
Ví dụ: Chúng ta có 2 Class: Thầy giáoNữ sinh cả 2 đều có chứa phương thức trả lời
void Answer();  Cả 2 đều là class con của Class Nguoi
Cài đặt:
Dừng lại ở đây, bạn nghĩ khi ng1->Answer();ng2->Answer(); 
Đáp án sẽ là gì?? Hãy code lại và chạy trước khi xem kết quả nhé!!
😯😯😯 Bất ngờ đúng không?
Thử nghĩ xem, rõ ràng ng1 nằm trong vùng nhớ của Nusinh lại in ra "Hello, I'm a human!!"????
Nguyên nhân ở đây là gì??? 
**Answer: Do chúng ta khai báo con trỏ ng1 ng2 thuộc kiểu Nguoi nên khi chạy chương trình, phương thức Answer() sẽ được ng1 chạy đến đầu tiên và cuối cùng in ra kết quả như trên, với ng2 cũng tương tự.
Giờ thử nếu khai báo biến đúng chỗ nhé

Và kết quả: 

Giờ mới đúng nè!!.




Để giải quyết vấn đề này, C++ đưa ra một khái niệm là phương thức trừu tượng. Bằng cách sử dụng phương thức trừu tượng. Khi gọi một phương thức từ một con trỏ đối tượng, trình biên dịch sẽ xác định kiểu của đối tượng mà con trỏ đang trỏ đến, sau đó nó sẽ gọi phương thức tương ứng với đối tượng mà con trỏ đang trỏ tới.


Khai báo phương thức trừu tượng 
Phương thức trừu tượng (còn gọi là phương thức ảo, hàm ảo) được khai báo với từ khoá virtual: 
  •  Nếu khai báo trong phạm vi lớp: 
    virtual <Kiểu trả về> <Tên phương thức>([<Các tham số>]); 
  • Nếu định nghĩa ngoài phạm vi lớp: 
    virtual <Kiểu trả về> <Tên lớp>::<Tên phương thức>([<Các tham 
    số>]){} 
Ví dụ: 
class Car{     
    public: 
          virtual void show(); 
};
là khai báo phương thức trừu tượng show() của lớp Car: phương thức không có tham số và không cần giá trị trả về (void).
Lưu ý: 
  • Từ khoá virtual có thể đặt trước hay sau kiểu trả về của phương thức. 
  • Với cùng một phương thức được khai báo ở lớp cơ sở lẫn lớp dẫn xuất, chỉ cần dùng từ khoá virtual ở một trong hai lần định nghĩa phương thức đó là đủ: hoặc ở lớp cơ sở, hoặc ở lớp dẫn xuất. 
  • Trong trường hợp cây kế thừa có nhiều mức, cũng chỉ cần khai báo phương thức là trừu tượng (virtual) ở một mức bất kì. Khi đó, tất cả các phương thức trùng tên với phương thức đó ở tất cả các mức đều được coi là trừu tượng. 
  • Đôi khi không cần thiết phải định nghĩa chồng (trong lớp dẫn xuất) một phương thức đã được khai báo trừu tượng trong lớp cơ sở. 

Sử dụng phương thức trừu tượng – đa hình 
Một khi phương thức được khai báo là trừu tượng thì khi một con trỏ gọi đến phương thức đó, chương trình sẽ thực hiện phương thức tương ứng với đối tượng mà con trỏ đang trỏ tới, thay vì thực hiện phương thức của lớp cùng kiểu với con trỏ. Đây được gọi là hiện tượng đa hình (tương ứng bội) trong C++. 
Chương trình sau ví dụ về việc sử dụng phương thức trừu tượng: lớp Nusinh kế thừa từ lớp Nguoi, hai lớp này cùng định nghĩa phương thức trừu tượng Answer().  
  • Khi ta dùng một con trỏ có kiểu lớp Nusinh trỏ vào địa chỉ của một đối tượng kiểu Nusinh, nó sẽ gọi phương thức Answer() của lớp Nusinh.  
  • Khi ta dùng cũng con trỏ đó, trỏ vào địa chỉ của một đối tượng kiểu Nguoi, nó sẽ gọi phương thức Answer() của lớp Nguoi
Xem ví dụ sau:
Sử dụng phương thức trừu tượng – đa hình 
Một khi phương thức được khai báo là trừu tượng thì khi một con trỏ gọi đến phương thức đó, chương trình sẽ thực hiện phương thức tương ứng với đối tượng mà con trỏ đang trỏ tới, thay vì thực hiện phương thức của lớp cùng kiểu với con trỏ. Đây được gọi là hiện tượng đa hình (tương ứng bội) trong C++. 
Chương trình sau ví dụ về việc sử dụng phương thức trừu tượng: lớp Bus kế thừa từ lớp Car, hai lớp này cùng định nghĩa phương thức trừu tượng show().  
  • Khi ta dùng một con trỏ có kiểu lớp Car trỏ vào địa chỉ của một đối tượng kiểu Car, nó sẽ gọi phương thức show() của lớp Car.  
  • Khi ta dùng cũng con trỏ đó, trỏ vào địa chỉ của một đối tượng kiểu Bus, nó sẽ gọi phương thức show() của lớp Bus. 
     
Xem ví dụ sau:
#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
 int  speed;               // Tốc độ  
 string  mark;           // Nhãn hiệu
 float price;               // Giá xe       
          // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
 Car();
 Car(int speed, string mark, float price);
 virtual void show(); // Giới thiệu xe, trừu tượng
 int getSpeed() {
  return speed;
 };
 string getMark() {
  return mark;
 };
 float getPrice() {
  return price;
 };

};

/* Khai báo phương thức bên ngoài lớp */
Car::Car() {
 this->speed = 0;
 this->mark = "";
 this->price = 0;

}
Car::Car(int speed, string mark, float price) {
 this->speed = speed;
 this->mark = mark;
 this->price = price;
}

void Car::show() {                // Phương thức hiển thị xe
 cout << "This is a " << mark << " having a speed of  " << speed << "km/h and its price is $" << price << endl;
}

/* Định nghĩa lớp Bus kế thừa từ lớp Car */
class Bus : public Car {
 int label;     // Số hiệu tuyến xe   
public:
 // Khởi tạo đủ tham số  
 Bus(int speed = 0, string mark = "", float price = 0, int lable = 0);
 void setLabel(int);  // Gán số hiệu tuyến xe     
 int getLabel();   // Đọc số hiệu tuyến xe 
 void show();
};

// Cài đặt lớp Bus
Bus::Bus(int speed, string mark, float price, int label) :Car(speed, mark, price) {
 this->label = label;
}
// Định nghĩa nạp chồng phương thức 
void Bus::show() {       // Giới thiệu xe bus 
 cout << "This is a bus of type " << getMark() << ", on the line "
  << label << ", having a speed of " << getSpeed()
  << "km / h and its price is $" << getPrice() << endl;
 return;
}

int main() {

 Car *ptrCar, myCar(100, "Ford", 3000);
 Bus myBus(150, "Mercedes", 5000, 27);// Biến đối tượng của lớp Bus
 ptrCar = &myCar;     // Trỏ đến đối tượng lớp Car 
 ptrCar->show();     // Phương thức của lớp Car 

 ptrCar = &myBus;     // Trỏ đến đối tượng lớp Bus 
 ptrCar->show();   // Phương thức của lớp Bus return; 
       // Hàm của lớp Bus 
 system("pause");

 return 0;
}


 Chương trình trên hiển thị kết quả thông báo như sau: 
This is a Ford having a speed of  100km/h and its price is $3000
This is a bus of type Mercedes, on the line 27, having a speed of 150km / h and its price is $5000


Nhận xét

Đăng nhận xét

Bài đăng phổ biến từ blog này

[Series] Thuật toán Sinh | Kì hai: Sinh hoán vị

Thuật Toán Interpolation Search - Tìm kiếm nội suy

Lời mở đầu!!