Lập trình tân binh | 2.8. Tính đa hình

Các bạn cảm thấy bài học kinh nghiệm về tính thừa kế thế nào ? Cá nhân tôi thì thấy nó vẫn còn khá khó dù tôi đã cố đơn giản hóa đi nhiều. Và thật buồn khi tôi phải thông tin với những bạn rằng bài học kinh nghiệm này cũng sẽ có mức độ khó tựa như. Đây không hề hoài nghi là 1 trong những bài học kinh nghiệm khó nhất trong giáo trình này. Tuy nhiên những thứ bạn nhận được cũng sẽ rất xứng danh .

Cùng với tính đóng gói và tính kế thừa, tính đa hình (polymorphism) là 1 trong 3 khái niệm nền tảng quan trọng nhất tạo nên sức mạnh của lập trình hướng đối tượng.

Đa hình có nghĩa là nhiều hình dạng. Tính chất này của lập trình hướng đối tượng người tiêu dùng được cho phép tất cả chúng ta hoàn toàn có thể thao tác với những đối tượng người dùng có năng lực biến hóa giải quyết và xử lý của bản thân tùy theo phương pháp mà chúng được sử dụng .

! Tôi đề nghị đọc kỹ lại bài học về con trỏ trước khi bắt đầu bài học này.

Phân giải liên kết

Chúng ta hãy cùng mở màn với 1 sự thừa kế đơn thuần. Trong bài học kinh nghiệm này, thay vì liên tục với game show RPG, tất cả chúng ta sẽ quay về với những ví dụ cổ xưa hơn như chương trình quản trị trạm bảo trì xe với những đối tượng người dùng phương tiện đi lại xe cộ .
Ở đây tất cả chúng ta sẽ thao tác với những lớp PhuongTien, XeOto và XeMay .

class PhuongTien{
   public:
     void hienThi() const; //Hien thi thong tin ve phuong tien

   protected:
     int m_giaTri; //Moi phuong tien co 1 gia tri nhat dinh
};

class XeOto : public PhuongTien{ //Oto la 1 phuong tien
   public:
     void hienThi() const;

   private:
     int m_soCua; //So cua cua xe oto
};

class XeMay : public PhuongTien{ //Xe may la 1 phuong tien
   public:
     void hienThi() const;

   private:
     double m_tocDo; //Toc do toi da cua xe may
};

Trong ví dụ này tôi không nêu hết các thông tin cũng như phương thức của các lớp này. Đừng ngại thêm vào các thành phần mà bạn muốn để lớp trở nên hoàn thiện hơn. Còn để trình bày điều tôi muốn nói thì vậy là đủ rồi sealed.

Mã xử lý của phương thức hienThi() :

void PhuongTien::hienThi() const{
   cout << "Day la 1 phuong tien giao thong." << endl;
}

void XeOto::hienThi() const{
   cout << "Day la 1 chiec xe oto." << endl;
}

void XeMay::hienThi() const{
   cout << "Day la 1 chiec xe may." << endl;
}

Mỗi lớp sẽ có cách tự giới thiệu khác nhau. Nếu các bạn nắm rõ bài học trước thì sẽ nhận ra tôi đã sử dụng phép ghi đè để định nghĩa lại phương thức hienThi() trong 2 lớp con.

Cùng thử chạy 1 đoạn mã ví dụ trong hàm main() nhé.

int main(){
   PhuongTien p;
   p.hienThi();   //"Day la 1 phuong tien giao thong."
   XeMay m;
   m.hienThi();   //" Day la 1 chiec xe may."

   return 0;
}

Kết quả không có gì giật mình và khó đoán trước .

Phân giải liên kết tĩnh

Nếu bây giờ chúng ta tạo ra thêm 1 hàm để gián tiếp sử dụng phương thức hienThi() trong main().

void gioiThieu(PhuongTien p){ //Gioi thieu phuong tien trong tham so
   p.hienThi();
}

int main(){
   PhuongTien p;
   gioiThieu(p);
   XeMay m;
   gioiThieu(m);

   return 0;
}

Về cơ bản thì đoạn mã này không khác nhiều so với đoạn mã trên. Thế nhưng những bạn sẽ thấy là tác dụng trả về có những biến hóa khá mê hoặc

Thông điệp in ra bởi đối tượng người tiêu dùng XeMay có yếu tố. Dường như là khi truyền tham số cho hàm, đối tượng người tiêu dùng đã mất đi tính chuyên biệt của XeMay mà trở về thành 1 PhuongTien bất kể .

? Vì sao lại thế ?

Bởi vì chúng ta đã sử dụng quan hệ kế thừa nên chúng ta biết rằng 1 XeMay là 1 PhuongTien với 1 số thuộc tính thêm vào. Hàm gioiThieu() thì có khả năng nhận vào tham số là 1 PhuongTien. Vậy nên nó cũng có thể nhận vào 1 XeOto hoặc 1 XeMay như chúng ta đã làm ở trên theo khái niệm về dẫn xuất kiểu dữ liệu.

Thế nhưng cần phải hiểu rằng, với trình biên dịch, ở bên trong hàm, chúng ta đang thao tác với 1 đối tượng PhuongTien mà không cần biết bản chất của nó vốn là XeMay hoặc XeOto. Thế nên nó sẽ sử dụng phương thức hienThi() của lớp PhuongTien thay vì sử dụng phiên bản thích hợp hơn nằm trong lớp XeMay như chúng ta mong đợi.

Trong ví dụ tôi sử dụng ở bài trước, trình biên dịch đưa ra được lựa chọn chính xác bởi vì nó đang thao tác bên trong lớp. Trong ví dụ hiện thời thì hàm gioiThieu() đang xét nằm bên ngoài các lớp nên nó không thể quyết định đúng được.

Thuật ngữ chuyên môn của hiện tượng này là phân giải liên kết tĩnh : hàm nhận vào tham số là 1 đối tượng PhuongTien thì sẽ luôn là các phương thức của lớp PhuongTien được sử dụng.

Phương thức nào được sử dụng được xác lập bởi chính kiểu tài liệu của tham số thay vì dựa trên thực chất thật của đối tượng người dùng .
Các bạn không nên lo ngại, tất cả chúng ta đã có sẵn cách để đối phó với việc này .

Phân giải liên kết động

Cái chúng ta muốn là trình biên dịch sẽ gọi đúng phiên bản của phương thức gioiThieu() mà chúng ta muốn, nghĩa là nó sẽ dựa trên bản chất của đối tượng được đưa vào làm tham số. Chúng ta gọi đây là phân giải liên kết động. Khi chạy, chương trình sẽ sử dụng đúng phương thức mà chúng ta muốn vì nó xác định được tham số là thực thể của lớp mẹ hay lớp con.

Để làm được việc này, tất cả chúng ta cần 2 thứ :

  • Con trỏ hoặc tham chiếu
  • Phương thức ảo

! Thiếu 1 trong 2 thứ trên thì tất cả chúng ta sẽ quay lại trường hợp mà máy tính không hề xác lập xem nên sử dụng phương pháp nào .

Phương thức ảo

Để sử dụng những phương pháp ảo, thứ nhất tất cả chúng ta cần phải hiểu được chúng nghĩa là gì .

Khai báo phương thức ảo

Việc này vô cùng đơn giản. Chúng ta chỉ cần thêm từ khóa virtual vào trước nguyên mẫu của phương thức của lớp, trong tệp .h.

class PhuongTien{
   public:
     virtual void hienThi() const; //Hien thi thong tin ve phuong tien

   protected:
     int m_giaTri; //Moi phuong tien co 1 gia tri nhat dinh
};

class XeOto : public PhuongTien{ //Oto la 1 phuong tien
   public:
     virtual void hienThi() const;

   private:
     int m_soCua; //So cua cua xe oto
};

class XeMay : public PhuongTien{ //Xe may la 1 phuong tien
   public:
     virtual void hienThi() const;

   private:
     double m_tocDo; //Toc do toi da cua xe may
};

! Việc thêm từ khóa virtual vào nguyên mẫu của phương thức trong các lớp con thật ra là không cần thiết bởi vì sẽ được kế thừa từ phương thức của lớp mẹ. Tuy nhiên tôi vẫn thường thêm chúng vào để dễ dàng ghi nhớ các phương thức đặc biệt.

Tới lúc này thì vẫn chưa có gì quá khó khăn vất vả cả. Các bạn cần chú ý quan tâm là không bắt buộc là hàng loạt những phương pháp của 1 lớp phải là phương pháp ảo. Chúng ta trọn vẹn hoàn toàn có thể có 1 lớp vừa có những phương pháp thường thì, vừa có những phương pháp ảo .

! Chú ý là chỉ cần thêm từ khóa virtual vào nguyên mẫu của phương thức trong tệp .h mà không cần thêm nó vào trong tệp .cpp.

Sử dụng tham chiếu

Tiếp theo, tất cả chúng ta sẽ cần sử dụng đến con trỏ hoặc tham chiếu. Chắc chắn là những bạn cũng giống như tôi, sẽ thích thao tác với tham chiếu hơn là thú vị với việc vật lộn thao tác con trỏ. Vậy nên tất cả chúng ta sẽ chọn giải pháp đơn thuần hơn, đó là tham chiếu .

void gioiThieu(PhuongTien const& p){
   p.hienThi();
}

int main(){
   PhuongTien p;
   gioiThieu(p);
   XeMay m;
   gioiThieu(m);
   return 0;
}

! Tôi sử dụng thêm từ khóa const để khai báo đây là tham chiếu hằng vì hàm này không thay đổi đối tượng.

Hàm gioiThieu() đã hoạt động đúng như những gì chúng ta muốn khi chọn ra được phương thức nào của đối tượng cần được sử dụng. Đấy là nhờ sự kết hợp của phương thức ảo và sử dụng tham chiếu. Bên cạnh đó, chúng ta cũng có thể nhận được hiệu quả tương tự khi thay tham chiếu bằng con trỏ.

Cùng 1 đoạn mã nhưng lại đưa ra những xử lý khác nhau tùy thuộc vào kiểu dữ liệu của tham số, đó là tính đa hình. Chúng ta cũng gọi những phương thức như gioiThieu() là các xử lý đa hình.

Các phương thức đặc biệt

? Theo các bạn thì những phương thức nào của lớp sẽ không bao giờ được kế thừa ?

Câu vấn đáp rất đơn thuần : những phương pháp khởi tạo và phương pháp hủy. Tất cả những phương pháp khác đều hoàn toàn có thể được thừa kế và mang theo những giải quyết và xử lý đa hình. Thế còn những phương pháp đặc biệt quan trọng này thì sao ?

Các phương thức khởi tạo

Liệu có sống sót những phương pháp khởi tạo ảo không ?

Câu trả lời hiển nhiên là không, bởi vì khi muốn tạo ra 1 đối tượng thì tôi sẽ biết là tôi muốn tạo ra cái gì và khi biên dịch thì đối tượng nào sẽ được tạo ra. Chính vì thế sẽ không tồn tại sự phân giải liên kết động khi gọi phương thức khởi tạo cũng như không được phép tồn tại phương thức khởi tạo ảo ! Điều này cũng dẫn đến việc chúng ta sẽ không thể gọi các phương thức ảo khác trong phương thức khởi tạo. Vậy nên đừng mất công thử vì dù bạn có cố làm thế thì sự phân giải liên kết động cũng sẽ không xảy ra.

Phương thức hủy

Đối với phương pháp hủy thì mọi thứ có chút rắc rối hơn .
Chúng ta sẽ thử với 1 ví dụ giải quyết và xử lý đa hình sử dụng con trỏ .

int main(){
   PhuongTien *p(0);
   p = new XeOto;
   //Tao ra 1 doi tuong xe oto va dua dia chi vao con tro kieu PhuongTien
   p->hienThi(); // "Day la 1 chiec xe oto."

   delete p;     //Xoa doi tuong oto
   return 0;
}

Chúng ta đã sử dụng phương thức ảo và con trỏ nên dong lệnh p->hienThi(); hiện ra đúng kết quả mà chúng ta muốn. Vấn đề nằm ở phép toán delete. Chúng ta có con trỏ nhưng phương thức đang thao tác lại không phải là 1 phương thức ảo. Vậy nên phương thức được gọi sẽ là phương thức hủy của PhuongTien chứ không phải của XeOto.

Vấn đề không quá nghiêm trọng trong ví dụ này nhưng không hề coi nhẹ nó. Nếu những bạn thao tác với những ứng dụng nhạy cảm như những ứng dụng nhúng trong động cơ máy bay và bạn gọi sai phương pháp hủy, động cơ hoàn toàn có thể không hoạt động giải trí và mọi thứ trở thành thảm họa trong nháy mắt .
Vậy nên tất cả chúng ta cần chắc như đinh gọi đúng phương pháp hủy của đối tượng người dùng. Không có quá nhiều lựa chọn, tất cả chúng ta phải tạo ra phương pháp hủy là 1 phương pháp ảo. Vậy nên tất cả chúng ta có thêm 1 quy tắc : 1 phương pháp hủy phải luôn là phương pháp ảo nếu tất cả chúng ta sử dụng những giải quyết và xử lý đa hình .
Chúng ta sẽ hoàn thành xong đoạn mã ví dụ bằng cách thêm vào phương pháp khởi tạo và phương pháp hủy .

class PhuongTien{
   public:
     PhuongTien(int giaTri);
     virtual void hienThi() const; //Hien thi thong tin ve phuong tien
     virtual ~PhuongTien();

   protected:
     int m_giaTri; //Moi phuong tien co 1 gia tri nhat dinh
};

class XeOto : public PhuongTien{ //Oto la 1 phuong tien
   public:
     XeOto(int giaTri, int soCua);
     virtual void hienThi() const;
     virtual ~XeOto();

   private:
     int m_soCua; //So cua cua xe oto
};

class XeMay : public PhuongTien{ //Xe may la 1 phuong tien
   public:
     XeMay(int giaTri, double tocDo);
     virtual void hienThi() const;
     virtual ~XeMay();

   private:
     double m_tocDo; //Toc do toi da cua xe may
}; 
PhuongTien::PhuongTien(int giaTri) : m_giaTri(giaTri){
}

void PhuongTien::hienThi() const{
   cout << "Day la 1 phuong tien giao thong co gia " << m_giaTri << " USD."<< endl;
}

PhuongTien::~PhuongTien(){} // Can them vao du khong co bat cu xu ly nao

XeOto:: XeOto(int giaTri, int soCua) : PhuongTien(giaTri), m_soCua(soCua){
}

void XeOto::hienThi() const{
   cout << "Day la 1 chiec xe oto co "<< m_soCua << " cua va co gia " << m_giaTri << " USD."<< endl;
}

XeOto::~XeOto(){}

XeMay::XeMay(int giaTri, double tocDoToiDa) : PhuongTien(giaTri), m_tocDo(tocDoToiDa){
}

void XeMay::hienThi() const{
   cout << "Day la 1 chiec xe may co toc do toi da la "<< m_tocDo << " km/h va co gia " << m_giaTri << " USD."<< endl;
}

XeMay::~XeMay(){}

Thế là tất cả chúng ta đã sẵn sàng chuẩn bị để tiếp xúc với những ví dụ đơn cử về giải quyết và xử lý đa hình trong phần sau của bài học kinh nghiệm .

Các tập hợp đa hình

Ví dụ của tất cả chúng ta là ứng dụng quản trị trạm bảo trì phương tiện đi lại, vậy nên nó cần quản trị được list những xe máy và ôtô ở trong trạm. Để quản trị những list này, tất cả chúng ta sẽ cần dùng đến … mảng động .

vector danhSachOto;
vector danhSachXeMay;

Tốt nhưng chưa phải là tối ưu. Nếu trong tương lai, trạm bảo dưỡng của chúng ta bảo dưỡng cả xe đạp, máy bay với cả xe tăng, vv… thì chúng ta sẽ phải tạo ra vô số vector. Đoạn mã của chúng ta sẽ cần thay đổi rất nhiều mỗi khi có thêm 1 kiêu phương tiện mới.

Ý tưởng sử dụng con trỏ

Sẽ thật tuyệt nếu tất cả chúng ta hoàn toàn có thể để tổng thể chúng chung vào 1 mảng. Bởi vì dù là xe máy hay ôtô thì đều là phương tiện đi lại, sáng tạo độc đáo là tất cả chúng ta sẽ tạo ra 1 mảng chứa những " phương tiện đi lại ". Bằng cách đó, tất cả chúng ta hoàn toàn có thể lưu trong mảng đó cả xe máy lẫn ôtô. Tuy nhiên, nếu tất cả chúng ta làm như vậy, tất cả chúng ta sẽ mất đi thông tin về thực chất của đối tượng người dùng. Vậy nên, tất cả chúng ta sẽ cần sử dụng đến mảng chứa tham chiếu hoặc mảng chứa con trỏ để thực thi những giải quyết và xử lý đa hình. Và do tại mảng chứa những tham chiếu thì không sống sót, tất cả chúng ta không có quá nhiều sự lựa chọn ngoài việc sử dụng mảng con trỏ để thao tác .
Đây là 1 ứng dụng khác của con trỏ ngoài những tính năng mà tất cả chúng ta đã từng nhắc đến trong bài học kinh nghiệm về con trỏ .

int main(){
   vector danhSachPhuongTien;
   return 0;
}

Đây là 1 tập hợp đa hình vì về cơ bản, nó hoàn toàn có thể chứa những thành viên thuộc nhiều kiểu tài liệu khác nhau .

Thao tác với tập hợp

Chúng ta sẽ mở màn bằng việc điền những giá trị vào mảng. Bởi vì tất cả chúng ta chỉ cần con trỏ để truy vấn tới những đối tượng người tiêu dùng PhuongTien của tất cả chúng ta nên không thiết yếu phải tạo ra từng cái tên cho những đối tượng người dùng và hoàn toàn có thể trực tiếp tạo ra chúng trải qua phân chia động rồi lưu trong mảng .

int main(){
   vector danhSachPhuongTien;

   danhSachPhuongTien.push_back(new XeOto(30000, 4));
   //Them vao danh sach 1 xe oto tri gia 15000 USD va co 4 cua
   danhSachPhuongTien.push_back(new XeOto(20000, 2));
   danhSachPhuongTien.push_back(new XeMay(5000, 200));
   //1 chiec xe may tri gia 5000 USD va co van toc toi da la 200 km/h

   return 0;
}

Các đối tượng người dùng không thực sự nằm trong những ô nhớ của mảng mà ở đấy chỉ có những con trỏ trỏ tới ô nhớ chứa chúng .

Khi dùng phân bổ động với new để tạo ra đối tượng, nhớ đừng quên sử dụng delete để giải phóng bộ nhớ sau khi dùng xong. Chúng ta sẽ cần sử dụng đến vòng lặp.

int main(){
   vector danhSachPhuongTien;

   danhSachPhuongTien.push_back(new XeOto(30000, 4));
   //Them vao danh sach 1 xe oto tri gia 15000 USD va co 4 cua
   danhSachPhuongTien.push_back(new XeOto(20000, 2));
   danhSachPhuongTien.push_back(new XeMay(5000, 200));
   //1 chiec xe may tri gia 5000 USD va co van toc toi da la 200 km/h

   for(int i(0); i < danhSachPhuongTien.size(); i++){
       delete danhSachPhuongTien[i]; //Giai phong o nho thu i
       danhSachPhuongTien[i] = 0; //Dua con tro ve 0
   }

   return 0;
}

Giờ thì chỉ còn việc sử dụng những đối tượng người dùng mà tất cả chúng ta vừa tạo ra thôi .

int main(){
   vector danhSachPhuongTien;

   danhSachPhuongTien.push_back(new XeOto(30000, 4));
   //Them vao danh sach 1 xe oto tri gia 15000 USD va co 4 cua
   danhSachPhuongTien.push_back(new XeOto(20000, 2));
   danhSachPhuongTien.push_back(new XeMay(5000, 200));
   //1 chiec xe may tri gia 5000 USD va co van toc toi da la 200 km/h

   danhSachPhuongTien [0]->hienThi();
   //Hien thi thong tin cua xe oto dau tien  
   danhSachPhuongTien [2]->hienThi();

   for(int i(0); i < danhSachPhuongTien.size(); i++){
       delete danhSachPhuongTien[i]; //Giai phong o nho thu i
       danhSachPhuongTien[i] = 0; //Dua con tro ve 0
   }
   return 0;
}

Trình biên dịch đã gọi đúng những phương pháp giải quyết và xử lý chính bới tất cả chúng ta đã thỏa mãn nhu cầu đủ những nhu yếu để phân giải link động .
Dành cho 1 số bạn muốn rèn luyện thêm, sau đây là 1 số nâng cấp cải tiến tất cả chúng ta hoàn toàn có thể thêm vào đoạn mã :

  • Thêm lớp XeTai dùng để mô tả các xe tải với 1 thuộc tính chỉ ra khối lượng xe tải này có thể chứa.
  • Thêm thuộc tính về năm sán xuất của phương tiện.
  • Lớp TramBaoDuong có 1 thuộc tính là danh sách phương tiện trong trạm. Lớp này cung cấp các phương thức để thêm và xóa phương tiện trong danh sách.
  • Thêm phương thức để lấy thông tin về số bánh xe của từng phương tiện.

Và ngoài những tổng thể những thứ mê hoặc những bạn hoàn toàn có thể tưởng tượng ra được về trạm bảo trì xe của tất cả chúng ta .

Các phương thức thuần ảo

Các bạn đã thử ý tưởng sáng tạo về phương pháp lấy thông tin về số bánh xe mà tôi nhắc đến ở trên chưa ? Nếu vẫn chưa thì giờ đây là lúc để thực thi nó đấy. Các bạn sẽ nhận thấy những điều rất mê hoặc .

Vấn đề

class PhuongTien{
   public:
     PhuongTien(int giaTri);
     virtual void hienThi() const; //Hien thi thong tin ve phuong tien
     virtual int soBanhXe() const; //Tra ve so banh cua phuong tien
     virtual ~PhuongTien();

   protected:
     int m_giaTri; //Moi phuong tien co 1 gia tri nhat dinh
};

class XeOto : public PhuongTien{ //Oto la 1 phuong tien
   public:
     XeOto(int giaTri, int soCua);
     virtual void hienThi() const;
     virtual int soBanhXe() const; //Tra ve so banh cu axe oto
     virtual ~XeOto();

   private:
     int m_soCua; //So cua cua xe oto
};

Không có vấn đề gì với tệp .h của chúng ta. Thế nhưng mã xử lý trong tệp .cpp lại không đơn giản như vậy.

int PhuongTien::soBanhXe() const{
   //Ket qua tra ve la bao nhieu ??????
}

int XeOto::soBanhXe() const{
   return 4;
}

Chúng ta không biết phải giải quyết và xử lý phương pháp của lớp PhuongTien như thế nào chính do số bánh xe của 1 phương tiện đi lại bất kể là không xác lập, hoàn toàn có thể là 2 bánh như xe máy hay 4 bánh như ôtô. Vấn đề là phương pháp này không mang 1 ý nghĩa đơn cử nào nhưng tất cả chúng ta lại không hề xóa nó trong lớp mẹ bởi nếu không có nó, tất cả chúng ta không thể thao tác được từ trong tập hợp .

Vậy nên chúng ta cần giữ phương thức này lại nhưng đồng thời cũng không cho phép trình biên dịch gọi nó. Nói cách khác, chúng ta cần thông báo cho trình biên dịch là trong tất cả các lớp con của PhuongTien sẽ có 1 phương thức soBanhXe() nhưng phương thức này lại không tồn tại trong PhuongTien.

Đây là cái mà chúng ta gọi là phương thức thuần ảo.

Để khai báo 1 phương thức là thuần ảo vô cùng đơn giản, các bạn chỉ cần thêm =0 vào cuối của nguyên mẫu của phương thức.

class PhuongTien{
   public:
     PhuongTien(int giaTri);
     virtual void hienThi() const; //Hien thi thong tin ve phuong tien
     virtual int soBanhXe() const = 0; //Tra ve so banh cua phuong tien
     virtual ~PhuongTien();

   protected:
     int m_giaTri; //Moi phuong tien co 1 gia tri nhat dinh
};

Trong tệp PhuongTien.cpp, chúng ta có thể không viết mã xử lý hoặc thậm chí xóa hẳn phương thức này vì nó không có ý nghĩa gì cả. Tất cả đã được thể hiện trong tệp .h.

Các lớp trừu tượng

1 lớp mà có chứa ít nhất 1 phương thức thuần ảo thì lớp đó là 1 lớp trừu tượng (abstract class). Vậy nên lớp PhuongTien là 1 lớp trừu tượng.

Tại sao phải đặt 1 tên đặc biệt quan trọng cho những lớp này ? Đó là chính do có 1 quy tắc quan trong cần ghi nhớ : không hề tạo ra 1 đối tượng người tiêu dùng từ 1 lớp trừu tượng
Đúng vậy, dòng lệnh sau sẽ không được biên dịch .

PhuongTien p(10000); //Tao ra 1 phuong tien gia 10000 USD.

Trong ngôn ngữ của lập trình viên thì chúng ta nói là không thể thực thể hóa 1 lớp trừu tượng. Lý do rất đơn giản : nếu có thể tạo ra 1 đối tượng PhuongTien thì sẽ có thể thông qua đó gọi phương thức soBanhXe() trong khi phương thức này không có mã xử lý. Vậy nên việc này là không thể.

Thế nhưng đoạn mã sau là trọn vẹn hợp lệ .

int main(){
   PhuongTien* ptr(0); //Con tro tro len 1 phuong tien
   XeOto lexus(250000,4);
   //Tao ra 1 doi tuong xe oto  
   ptr = &lexus //Huong con tro len doi tuong vua tao
   cout << ptr->soBanhXe() << endl;
   //Trong lop con, co ton tai phuong thuc soBanhXe() nen khong co loi bien dich

   return 0;
}

Trong trường hợp này, phương thức soBanhXe() là 1 xử lý đa hình nên phương thức được gọi và sử dụng là phương thức được định nghĩa trong lớp XeOto. Vậy nên dù phương thức trong lớp PhuongTien không tồn tại thì cũng không dẫn đến lỗi biên dịch.

Nếu các bạn muốn tạo ra thêm các lớp con khác của PhuongTien như XeTai, chúng ta sẽ bắt buộc phải định nghĩa lại phương thức soBanhXe() cho nó nếu không phương thức này sẽ thuần ảo do kế thừa từ lớp mẹ và lớp XeTai của chúng ta cũng sẽ trở thành 1 lớp trừu tượng.

Nói tóm lại :

  • 1 phương thức ảo có thể được định nghĩa lại trong lớp con.
  • 1 phương thức thuần ảo bắt buộc phải được định nghĩa lại trong lớp con.

Trong thư viện Qt mà chúng ta tìm hiểu tới đây sẽ có rất rất nhiều các lớp trừu tượng, ví dụ như lớp QAbstractButton chứa rất nhiều điểm chung của các nút bấm. Thế nhưng để người dùng không thể trực tiếp thực thể hóa lớp này mà phải sử dụng các lớp thừa kế của nó, những người viết nên thư viện này đã khai báo đây là 1 lớp trừu tượng.

Tóm tắt bài hoc :

  • Tính đa hình cho phép thao tác các đối tượng thuộc lớp con thông qua con trỏ hoặc tham chiếu trỏ lên kiểu dữ liệu lớp mẹ.
  • Để thực hiện xử lý đa hình cần sử dụng phương thức ảo kết hợp với con trỏ hoặc tham chiếu.
  • Nếu 1 phương thức không thể xác định được mã xử lý thì có thể được khai báo như là 1 phương thức thuần ảo.
  • 1 lớp có chứa các phương thức thuần ảo là 1 lớp trừu tượng. Chúng ta không thể tạo ra, hay chính xác hơn là thực thể hóa đối tượng của lớp này.