Lập trình tân binh | 2.7. Tính kế thừa

Lần này, tôi xin phép được giới thiệu thêm 1 khái niệm nền tảng của lập trình hướng đối tượng, cùng với tính đóng gói mà các bạn đã tìm hiểu trong bài trước, đấy là tính kế thừa (inheritance). Đây có thể coi là 1 bộ phận không thể thiếu tạo nên sức mạnh của OOP.

Trong bài học kinh nghiệm này, tôi sẽ quay lại với ví dụ của lớp NhanVat, nhưng được giản lược đi nhiều để tất cả chúng ta hoàn toàn có thể tập trung chuyên sâu vào trọng điểm .
Thế nhưng, những bạn cũng đừng quá lo ngại vì khái niệm này không hề quá khó hiểu dù quyền lợi mà nó mang lại cho tất cả chúng ta vô cùng to lớn .

Ví dụ về tính kế thừa

Nhiều người sẽ thấy lạ khi nghe về sự kế thừa ở trong lập trình tin học nhưng thật sự thì khái niệm này không quá rắc rối. Đấy vốn là kỹ thuật được cho phép những lập trình viên tạo ra 1 lớp dựa trên 1 lớp khác có sẵn. Lớp khởi đầu sẽ trở thành nền tảng của lớp mới mà tất cả chúng ta muốn tạo ra. Kỹ thuật này sẽ được cho phép tất cả chúng ta không cần viết lại nhiều lần đoạn mã giải quyết và xử lý thực thi cùng 1 việc làm .

Làm sao nhận biết sự kế thừa ?

Câu hỏi hay được đặt ra là « Trong OOP thì làm thế nào để biết khi nào thì cần sử dụng tính kế thừa ? ». Nhiều người từng đau khổ vì khái niệm này thì nhìn thấy nó ở khắp mọi nơi. Một số người khác, phần lớn là những tân binh mới học lập trình, thì mỗi lần luôn phải tự đặt lại câu hỏi. Thế nhưng tôi sẽ cho những bạn 1 tuyệt kỹ để hoàn toàn có thể nhận ra khi nào cần đến sự kế thừa trong lập trình hướng đối tượng người tiêu dùng. Nó rất đơn thuần và cực kỳ đúng chuẩn .
Các bạn biết là cần sử dụng đến sự kế thừa nếu tất cả chúng ta hoàn toàn có thể khẳng định chắc chắn : A là B .
Tôi nghĩ là 1 ví dụ đơn cử hơn sẽ rất có ích. Trò chơi của tất cả chúng ta có rất nhiều kiểu nhân vật : chiến binh, phù thủy, thích khách, vv … Rất phong phú với những đặc thù khác nhau, thế nhưng chúng đều là những nhân vật. Tôi hoàn toàn có thể nói « chiến binh là 1 nhân vật » hay « phù thủy là 1 nhân vật ». Vậy nên nếu tất cả chúng ta tạo ra 2 lớp ChienBinh và PhuThuy thì cả 2 lớp này sẽ đều kế thừa lớp NhanVat mà tất cả chúng ta đã tạo ra trước đó .
Sau đây là 1 số ví dụ khác hay được sử dụng để diễn đạt tính kế thừa :

  • Xe máy là 1 loại phương tiện nên lớp XeMay kế thừa lớp PhuongTien.
  • Xe đạp là 1 loại phương tiện nên lớp XeDap kế thừa lớp PhuongTien.
  • Khủng long bạo chúa là 1 loài khủng long nên lớp KhungLongBaoChua kế thừa lớp KhungLong.
  • Khủng long là 1 loại động vật nên lớp KhungLong kế thừa lớp DongVat.

trái lại, tất cả chúng ta sẽ không hề nói « xe máy là 1 loại động vật hoang dã » thế nên sẽ không có sự kế thừa nào ở đây cả .
! Hãy rất quan tâm tôn trong quy tắc trên nếu không những bạn có rủi ro tiềm ẩn phát hiện những yếu tố lớn về logic trong đoạn mã nguồn .
Trong phần tiếp theo, tất cả chúng ta sẽ xem cách thực thi sự kế thừa trong C + + qua lớp NhanVat .

Lớp NhanVat

Nhắc lại 1 chút thì đây là lớp miêu tả 1 nhân vật trong siêu cấp game show RPG của tất cả chúng ta. Các bạn không cần biết chơi để hoàn toàn có thể theo dõi tiếp ví dụ. Cá nhân tôi thấy nó đỡ khô khan hơn nhiều so với những ví dụ vẫn hay được sử dụng bởi những thấy dạy lập trình ở trường .
Tôi sẽ đơn giản hóa 1 chút lớp NhanVat mà tất cả chúng ta đã tạo ra ở bài trước .

#ifndef DEF_NHANVAT
#define DEF_NHANVAT
#include 
#include 

class NhanVat{
   public:
       NhanVat();
       void nhanSatThuong(int dmg);
       void tanCong(NhanVat &mucTieu) const;

   private:
       int m_hp;
       std::string m_ten;
};
#endif

Nhân vật của tất cả chúng ta sẽ có 1 cái tên và 1 số điểm hp. Chúng ta chỉ sử dụng 1 phương pháp khởi tạo mặc định được cho phép người chơi đặt tên cho nhân vật và khởi đầu với 100 điểm hp .

Nhân vật có thể chịu sát thương thông qua phương thức nhanSatThuong và tạo ra sát thương cho nhân vật khác qua phương thức tanCong.

Dưới đây là ví dụ 1 đoạn mã xử lý nằm trong tệp NhanVat.cpp.

#include "NhanVat.h"
using namespace std;

NhanVat::NhanVat() : m_hp(100), m_ten("BuBu"){
}

void NhanVat::nhanSatThuong(int dmg){
   m_hp -= dmg;
}

void NhanVat::tanCong(NhanVat &mucTieu) const{
   mucTieu.nhanSatThuong(10);
}

Những kỹ năng và kiến thức trên đây tất cả chúng ta đều đã từng bàn luận qua .

Lớp ChienBinh kế thừa lớp NhanVat

Sau đây sẽ là tiết mục chính : tất cả chúng ta muốn tạo ra 1 lớp con của lớp NhanVat. Nói cách khác, lớp mới này sẽ kế thừa lớp NhanVat .

Tôi sẽ tạo ra 1 lớp ChienBinh để mô tả các nhân vật chiến binh. Định nghĩa lớp này trong tệp ChienBinh.h sẽ như sau :

#ifndef DEF_CHIENBINH
#define DEF_CHIENBINH
#include 
#include 
#include "NhanVat.h"
//Dung quen them NhanVat.h neu ban muon ke thua lop NhanVat

class ChienBinh : public NhanVat{
//Y nghia cua dong tren la lop ChienBinh ke thua lop NhanVat
};

#endif

Dựa theo đoạn mã bên trên thì cơ lớp ChienBinh sẽ chiếm hữu tổng thể những thuộc tính cũng như phương pháp của lớp NhanVat .
Trong trường hợp đấy thì tất cả chúng ta sẽ gọi lớp NhanVat là « lớp mẹ » còn lớp ChienBinh là « lớp con » .

? Tạo ra thêm 1 lớp chứa cùng các thuộc tính và phương thức thì có lợi ích gì ?

Lợi ích là chúng ta có thể thêm các thuộc tính và phương thức đặc biệt chỉ có ở các nhân vật chiến binh vào lớp ChienBinh. Ví dụ như tôi sẽ thêm vào đây phương thức tanCongLienTiepBangRiu.

#ifndef DEF_CHIENBINH
#define DEF_CHIENBINH
#include 
#include 
#include "NhanVat.h"
//Dung quen them NhanVat.h neu ban muon ke thua lop NhanVat

class ChienBinh : public NhanVat{
  public:
    void tanCongLienTiepBangRiu() const;
};

#endif

Chúng ta sẽ có sơ đồ sau :

Ý nghĩa của sơ đồ là : lớp ChienBinh kế thừa lớp NhanVat nên sở hữu tất cả đặc tính của lớp này (có thuộc tính tên, số điểm hp và có phương thức nhận sát thương). Chúng ta nói rằng lớp ChienBinh được chuyên biệt hóa từ lớp NhanVat. Ngoài ra nó sở hữu thêm 1 phương thức của riêng mình là tanCongLienTiepBangRiu().

! Cần chú ý quan tâm là trong phép kế thừa thì lớp con sẽ kế thừa toàn bộ, nghĩa là cả phương pháp lẫn thuộc tính của lớp mẹ .
Các bạn đã khởi đầu tưởng tượng ra rồi chứ ? Trong C + +, nếu 2 đối tượng người dùng hoàn toàn có thể được miêu tả theo quan hệ « đối tượng người tiêu dùng này là đối tượng người tiêu dùng kia » thì tất cả chúng ta sẽ sử dụng đến tính kế thừa. Đối tượng ChienBinh hoàn toàn có thể được coi là 1 tăng cấp của đối tượng người dùng NhanVat với những tính năng mới thêm vào .
Khái niệm này tưởng chúng như đơn thuần nhưng sẽ trở nên vô cùng can đảm và mạnh mẽ khi những bạn đã đồng cảm nó. Chúng ta sẽ có nhiều thời cơ để thực hành thực tế với nó trong phần tiếp theo của giáo trình .

Lớp PhuThuy cũng kế thừa lớp NhanVat

1 sự kế thừa thì không mang lại nhiều độc lạ nhưng khi mà nhiều sự kế thừa được sử dụng phối hợp với nhau thì sức mạnh của khái niệm này từ từ bộc lộ ra .
Tiếp theo tất cả chúng ta sẽ tạo ra lớp PhuThuy cũng kế thừa lớp NhanVat, do tại 1 phù thủy cũng là 1 nhân vật. Vậy nên 1 phù thủy cũng sẽ có tên và số điểm hp thuộc về mình cũng như hoàn toàn có thể sử dụng phép tiến công đơn thuần .
Khác biệt là 1 phù thủy còn phải thực thi được phép thuật như tạo phun ra băng hoặc lửa, vv … Để sử dụng những phép này thì phù thủy cần tiêu tốn điểm mana ( 1 thuộc tính mới mà tất cả chúng ta cần thêm vào ). Khi nào điểm mana quay trở lại 0 thì phù thủy sẽ không hề làm phép nữa .

#ifndef DEF_PHUTHUY
#define DEF_PHUTHUY
#include 
#include 
#include "NhanVat.h"

class PhuThuy : public NhanVat{
   public:
       void phunLua();
       void phunBang();

   private:
       int m_mp;
};

#endif

Điều quan trọng là những bạn cần hiểu rõ nguyên tắc hoạt động giải trí của sự kế thừa .

Trong sơ đồ trên thì tôi không đề cập tới những thuộc tính nhưng chúng cũng vẫn được kế thừa không khác gì những phương pháp .
Và thậm chí còn còn thần kỳ hơn là tất cả chúng ta hoàn toàn có thể tạo ra 1 lớp con kế thừa 1 lớp mẹ mà bản thân lớp mẹ này cũng là lớp con của một lớp khác ! Hãy tưởng tượng là tất cả chúng ta sẽ có 2 loại phù thủy : phù thủy đen chuyên tiến công và phù thủy trắng chuyên chữa thương. Mọi thứ sẽ trở nên thật mê hoặc .

Chúng ta hoàn toàn có thể cứ liên tục mãi như vậy …

Các bạn sẽ thấy là trong thư viện Qt mà chúng ta tìm hiểu ở chương sau có thể xuất hiện đến 5, 6 sự kế thừa chồng lên nhau là bình thường. Đừng vội, rồi các bạn sẽ thấy nhiều hơn!

Dẫn xuất kiểu dữ liệu

Chúng ta hãy xem xét đoạn mã sau đây :

NhanVat nhanVat;
ChienBinh chienBinh;

nhanVat.tanCong(chienBinh);
chienBinh. tanCong(nhanVat);

Đoạn mã trên hoạt động giải trí tốt. Thế nhưng nếu những bạn chịu chú ý quan tâm kỹ, nhiều bạn sẽ vướng mắc là làm thế nào mà đoạn mã giải quyết và xử lý trên hoàn toàn có thể chạy được do tại theo những gì tất cả chúng ta đã bàn luận qua, đoạn mã trên sẽ phải báo lỗi .

Nếu vẫn còn có bạn chưa theo kịp thì vấn đề của chúng ta như thế này : hãy xem nguyên mẫu của phương thức tanCong.

void tanCong(NhanVat &mucTieu) const;

Phương thức này là giống nhau dù là trong lớp NhanVat hay lớp ChienBinh do tại ChienBinh đã kế thừa từ NhanVat .

Khi chúng ta thực hiện chienBinh.tanCong(nhanVat);, tham số của chúng ta là 1 đối tượng NhanVat đúng như nguyên mẫu.

Thế nhưng trong câu lệnh nhanVat.tanCong(chienBinh);, chúng ta lại truyền 1 tham số ChienBinh cho phương thức này. Vậy làm thế quái nào mà trình biên dịch không trả về cho chúng ta 1 thông báo lỗi như thường lệ? Nó thậm chí vẫn biên dịch ra được 1 đoạn mã máy hoạt động 1 cách bình thường !

Thật ra thì bí hiểm của điều thần kỳ này đến từ 1 đặc thù của tính kế thừa trong C + + : nó được cho phép tất cả chúng ta 1 đối tượng người dùng của lớp con thay thế sửa chữa đối tượng người tiêu dùng của lớp mẹ trong bất kể giải quyết và xử lý nào có tương quan đến con trỏ hoặc tham chiếu trên lớp mẹ .
Điều này nghĩa là tất cả chúng ta trọn vẹn có quyền viết 1 đoạn mã như sau :

NhanVat *nhanVat(0);
ChienBinh *chienBinh = new ChienBinh();
nhanVat = chienBinh; // Qua than ky roi !!!

2 dòng lệnh tiên phong trọn vẹn không có gì đặc biệt quan trọng. Chúng ta chỉ đơn thuần là tạo ra 1 con trỏ lên kiểu NhanVat rồi gán cho nó giá trị là 0 và khởi tạo 1 con trỏ khác lên đối tượng người dùng kiểu ChienBinh .
Thế nhưng dòng lệnh cuối thì khá kỳ lạ vì tất cả chúng ta đã gán lẫn lộn giữa con trỏ lên 2 kiểu tài liệu khác nhau .
Nếu trong những trường hợp khác thì trình biên dịch sẽ vô cùng không hài lòng và không được cho phép tất cả chúng ta thực thi điều này. Thế nhưng ChienBinh vốn là lớp con của NhanVat. Vậy nên mọi thứ trở nên hài hòa và hợp lý chính do nói cho cùng thì ChienBinh cũng là 1 NhanVat. Vì thế quy tắc cần nhớ cho những bạn đó là tất cả chúng ta hoàn toàn có thể gán giá trị 1 thành phần của lớp con cho 1 thành phần của lớp mẹ .

! Tuy nhiên cần chú ý là phép toán theo chiều ngược lại là sai. Chúng ta không thể viết chienBinh = nhanVat; vì trình biên dịch cấm làm thế và sẽ làm treo chương trình. Hãy chú ý đến chiều của phép gán.

Vậy là không có gì ngăn cản tất cả chúng ta sử dụng 1 đối tượng người tiêu dùng vào 1 ví trí đáng ra thuộc về đối tượng người dùng khái quát hơn. Việc này giúp ích rất nhiều khi tất cả chúng ta thao tác với những tham số của hàm. Ví dụ :

void tanCong(NhanVat &mucTieu) const;

Phương thức tanCong này cho phép chúng ta tấn cống bất cứ kiểu nhân vật nào dù là chiến binh, phù thùy, phù thủy trắng hay phù thủy đen bởi vì suy cho cùng thì chúng đều là 1 nhân vật.

Có thể một số bạn sẽ thấy hơi lạ lẫm lúc ban đầu nhưng rồi sẽ nhận ra là mọi chuyện hoàn toàn hợp lý. Trong thực tế, phương thức tanCong của chúng ta sẽ đơn giản là gọi phương thức nhanSatThuong vẫn tồn tại trong tất cả các lớp.

Nếu những bạn vẫn thấy khó hiểu thì xin hãy đọc lại phần bên trên thêm 1 lần nữa .

? Tôi vẫn không thể hiểu được tại sao phép gán doiTuongMe = doiTuongCon; có thể thực hiện được. Theo tôi thấy thì đối tượng con sở hữu nhiều thuộc tính mà đối tượng mẹ không có. Vậy thì phép gán phải có vấn đề mới đúng chứ ? Theo chiều ngược lại không hợp lý hơn à ?

Thực ra, sai lầm của bạn là ở chỗ cho rằng chúng ta đã thực hiện phép gán giá trị các đối tượng trong khi không phải thế. Chúng ta chỉ đơn giản là thay thế 1 con trỏ. 2 xử lý trên hoàn toàn khác hẳn nhau. Các đối tượng vẫn tồn tại trong bộ nhớ mà không hề chịu bất cứ thay đổi nào. Việc chúng ta làm chỉ là hướng con trỏ về phía đối tượng con. Đối tượng con này sẽ bao gồm 2 thành phần : phần được thừa kế từ lớp mẹ và phần thuộc về riêng nó. Xử lý lệnh doiTuongMe = doiTuongCon; chỉ đơn giản hướng con trỏ doiTuongMe vào phần chứa những thuộc tính và phương thức kế thừa của doiTuongCon mà thôi.

Tôi khó có thể giải thích rõ ràng hơn nữa, hy vọng là các bạn vẫn có thể hiểu được. Nếu không thì các bạn hãy nhớ lấy quy tắc mà tôi đã nhắc đến ở trên. Ít nhất thì nó sẽ giúp các bạn sống sót với C++ trong 1 thời gian dài dù không hiểu rõ cơ chế của xử lý dạng này.

Dù sao thì các bạn nên biết là kỹ thuật này rất hữu dụng trong C++ và được sử dụng khá thường xuyên. Chúng ta sẽ thực hành nhiều hơn về nó trong phần sau khi các bạn học cách sử dụng Qt.

Sự kế thừa và phương thức khởi tạo

Từ đầu bài học kinh nghiệm đến giờ, tôi vẫn chưa hề nhắc đến phương pháp khởi tạo. Vậy nên phần tiếp theo đây sẽ dành để nói về quan hệ giữa phương pháp này và sự kế thừa .
Chúng ta đều biết lớp NhanVat có 1 phương pháp khởi tạo mặc định .

NhanVat();

… và mã giải quyết và xử lý của nó :

NhanVat::NhanVat() : m_hp(100), m_ten("BuBu"){
}

Khi mà tất cả chúng ta muốn tạo ra 1 đối tượng người dùng NhanVat thì phương pháp này là phương pháp sẽ được gọi trước hết .
Vậy chuyện gì sẽ xảy ra khi giờ đây tất cả chúng ta muốn tạo ra 1 đối tượng người dùng PhuThuy kế thừa lớp NhanVat ?
Lớp PhuThuy bản thân nó cũng có phương pháp khởi tạo của riêng mình. Liệu nó sẽ ảnh hưởng tác động gì tới phương pháp khởi tạo của NhanVat không ? Ngoài ra, nó cũng cần gọi phương pháp khởi tạo của NhanVat nếu không thì sẽ không hề gán giá trị khởi đầu cho thuộc tính điểm hp và tên được .
Thực ra, những giải quyết và xử lý sẽ được thực thi theo thứ tự sau :

  1. Chúng ta yêu cầu tạo ra đối tượng PhuThuy.
  2. Trình biên dịch trước tiên goi ra phương thức khởi tạo của lớp mẹ (NhanVat).
  3. Tiếp đó, trình biên dịch gọi phương thức khởi tạo của lớp con.

Tức là phương pháp khởi tạo của lớp mẹ sẽ luôn được gọi trước phương pháp của lớp con và rồi tiếp sau lớp con sẽ là lớp cháu nếu có sự Open của sự kế thừa chồng chất .

Sử dụng phương thức khởi tạo của lớp mẹ

Để hoàn toàn có thể trước hết gọi sử dụng phương pháp khởi tạo của lớp mẹ, những bạn cần gọi nó từ trong phương pháp tạo của lớp PhuThuy. Trong những trường hợp thế này, những bạn sẽ thấy hiệu suất cao thiết thực của kỹ thuật khởi tạo bằng list giá trị .

PhuThuy::PhuThuy() : PhuThuy(), m_mp(100){
}

Theo như trong list, tất cả chúng ta cần thứ nhất gọi phương pháp khởi tạo của lớp NhanVat rồi mới tới khởi tạo giá trị cho những thuộc tính riêng của lớp PhuThuy ( ở đây là số điểm mp ) .
! Khi tất cả chúng ta tạo ra đối tượng người dùng PhuThuy thì trình biên dịch sử dụng phương pháp khởi tạo mặc định không nhu yếu tham số của lớp mẹ .

Truyền tham số

Một trong những quyền lợi lớn nhất của sự kế thừa là được cho phép tham số được truyền đi giữa những phương pháp tạo của NhanVat và PhuThuy. Ví dụ nếu phương pháp khởi tạo của NhanVat được cho phép nhận vào 1 tham số là tên của nhan vật thì phương pháp khởi tạo của lớp PhuThuy cũng phải được cho phép nhận vào tham số này để truyền cho phương thức thức của lớp NhanVat .

PhuThuy::PhuThuy(string ten) : NhanVat(ten), m_mana(100){
}

NhanVat::NhanVat(string ten) : m_vie(100), m_ten(ten){
}

Và đây chính là cách mà tất cả chúng ta bảo vệ những đối tượng người dùng được tạo ra đúng chuẩn như mong ước .
Dưới đây là 1 sơ đồ đơn thuần để giúp những bạn hiểu rõ hơn tiến trình của những giải quyết và xử lý tất cả chúng ta vừa nhắc đến .

Chúng ta muốn tạo ra 1 nhân vật phù thủy merlin. Nhưng đây là 1 đối tượng người dùng, vậy nên trình biên dịch sẽ gọi phương pháp khởi tạo của lớp PhuThuy. Phương thức khởi tạo của PhuThuy lại nhu yếu trình biên dịch gọi phương pháp khởi tạo của lớp mẹ là lớp NhanVat nên trình biên dịch sẽ mở màn từ triển khai mã giải quyết và xử lý của phương pháp này. Sau khi hoàn thành xong thì nó quay lại và triển khai mã của phương pháp khởi tạo lớp PhuThuy .
Cuối cùng, sau khi mọi giải quyết và xử lý đều được triển khai xong và đối tượng người tiêu dùng merlin được tạo ra thì tất cả chúng ta hoàn toàn có thể khởi đầu sử dụng nhân vật này cho mục tiêu của tất cả chúng ta .

Quyền truy cập protected

Chúng ta sẽ không hề nhắc đến sự kế thừa mà bỏ lỡ không nói tới khái niệm về quyền truy vấn protected .
Trước đó tất cả chúng ta đã tìm hiểu và khám phá về 2 loại quyền truy vấn :

  • public : dành cho những thành phần có thể được truy cập từ bên ngoài lớp.
  • private : dành cho những thành phần không thể được truy cập từ bên ngoài lớp.

Những quyền truy vấn trên được cho phép tất cả chúng ta tuân thủ quy tắc vàng về tính đóng gói của C + +. Quyền truy vấn protected là 1 quyền truy vấn khác mà tôi xếp vào khoảng chừng giữa của public và private. Nó chỉ mang 1 ý nghĩa đơn cử nếu ở trong lớp chủ thể của sự kế thừa ( lớp mẹ ) nhưng những bạn hoàn toàn có thể sử dụng nó trong toàn bộ những lớp nếu muốn kể cả là trong trường hợp không có sự kế thừa nào .
Ý nghĩa của quyền truy vấn này là những thành phần của lớp được bảo vệ bởi nó sẽ không hề được truy vấn từ bên ngoài trừ khi là từ 1 lớp con của lớp sử dụng protected .
Nói cách khác, nếu những thành phần của lớp NhanVat được bảo vệ bởi quyền protected thì chúng hoàn toàn có thể được truy vấn từ những lớp con của lớp NhanVat là ChienBinh và PhuThuy. Chúng ta sẽ không hề truy vấn như thể nếu quyền truy vấn được sử dụng là quyền private .
! Trong thực tiễn thì tôi luôn bảo vệ những thuộc tính của những lớp mà tôi tạo ra với quyền truy vấn protected. Kết quả là tôi vẫn bảo vệ được tính đóng gói trừ khi là trong trường hợp có sử dụng tính kế thừa lên những lớp đó .
Việc này là thiết yếu để giảm bớt đáng kể những phương pháp lấy và phương pháp đặt cần phải tạo ra và giúp đoạn mã của tất cả chúng ta bớt nặng nề đi nhiều .

class NhanVat{
   public:
       NhanVat();
       NhanVat(std::string ten);
       void nhanSatThuong(int dmg);
       void tanCong(NhanVat &mucTieu) const;

   protected: //Chi co the duoc truy cap tu cac lop con (ChienBinh, PhuThuy)
     int m_hp;
     std::string m_ten;
};

Chúng ta hoàn toàn có thể tự do thao tác với những thuộc tính của NhanVat như số điểm hp và tên từ trong những lớp con như ChienBinh hay PhuThuy .

Ghi đè phương thức

Chúng ta sẽ kết thúc bài học này với khái niệm ghi đè (override) các phương thức giữa lớp con và lớp mẹ.

Phương thức của lớp mẹ

Trò chơi của chúng ta đôi khi đòi hỏi nhân vật tự giới thiệu về bản thân mình. Bởi vì đây là 1 hành động chung của tất cả các kiểu nhân vật, bất kể là chiến binh hay phù thùy, nên vị trí thích hợp nhất cho phương thức tuGioiThieu() là trong khai báo của lớp NhanVat.

class NhanVat{
   public:
       NhanVat();
       NhanVat(std::string ten);
       void nhanSatThuong(int dmg);
       void tanCong(NhanVat &mucTieu) const;
       void tuGioiThieu() const;

   protected:
     int m_hp;
     std::string m_ten;
};

! Đến giờ chắc mọi người đều đã quá quen thuộc với từ khóa const. Ở đây nó biểu thị là phương thức sẽ không được phép thay đối đối tượng chủ thể.

Mã xử lý của phương thức này trong tệp .cpp :

void NhanVat::tuGioiThieu() const{
   cout << "Xin chao, toi ten la " << m_ten<< "." << endl;
   cout << "Toi con " << m_hp << " diem hp." << endl;
}

Và gọi sử dụng phương thức này trong hàm main().

int main(){
   NhanVat bubu("BuBu");
   bubu.tuGioiThieu();

   return 0;
}

Kết quả tất cả chúng ta sẽ nhận được là :

Phương thức kế thừa trong lớp con

Chiến binh cũng là 1 nhân vật nên cũng hoàn toàn có thể tự trình làng về bản thân mình .

int main(){
   ChienBinh gauChienBinh("Gau Chien Binh");
   gauChienBinh.tuGioiThieu();
   return 0;
}

Nhân vật Gấu Chiến binh ra mắt về mình .

Ghi đè

Bây giờ tất cả chúng ta muốn rằng những chiến binh sẽ tự ra mắt khác đi một chút ít : phải thêm câu ra mắt thông tin mình là chiến binh. Vậy nên tất cả chúng ta cần viết thêm 1 phiên bản khác của phương pháp này cho lớp ChienBinh .

void ChienBinh::tuGioiThieu() const{
   cout << " Xin chao, toi ten la " << m_ten<< "." << endl;
   cout << " Toi con " << m_hp << " diem hp." << endl;
   cout << "Toi la 1 chien binh dung manh." << endl;
}

? Vậy là có 2 phương thức với cùng tên và cùng tham số trong 1 lớp à ? Chúng ta đâu được phép làm thế ?

Các bạn có phần đúng, cũng có phần sai. 2 hàm vốn không hề có nguyên mẫu giống nhau ( giống tên và giống tham số ), thế nhưng lại có ngoại lệ trong trường hợp những lớp kế thừa. Phương thức của lớp ChienBinh sẽ sửa chữa thay thế phương pháp được kế thừa từ lớp NhanVat .

Nếu chúng ta sử dụng phương thức này trong main() thì sẽ nhận được kết quả đúng như chúng ta mong đợi.

Khi tất cả chúng ta tạo ra 1 phương pháp có giống với phương pháp kế thừa từ lớp mẹ thì đó là sự ghi đè. Phương thức của lớp mẹ sẽ bị ghi đè và bị che khuất đi .
! Để che khuất 1 phương pháp thì chỉ cần sử dụng cùng tên với phương pháp được kế thừa là đủ, không quan trọng số và kiểu tham số truyền cho phương pháp .

Tính năng này khá là thực dụng ! Khi thực hiện kế thừa, lớp con sẽ tự động nhận được tất cả các phương thức của lớp mẹ. Nếu trong số đó có phương thức mà chúng ta muốn thay đổi, chúng ta sẽ viết lại nó trong lớp con và trình biên dịch sẽ biết là phải gọi phương thức nào. Trong ví dụ thì nếu nhân vật là 1 chiến binh thì trình biên dịch sẽ sử dụng phiên bản trong lớp ChienBinh của tuGioiThieu(), trong những trường hợp còn lại như NhanVat hay PhuThuy thì nó sẽ sử dụng phương thức cơ bản trong lớp NhanVat.

Rút gọn mã nguồn

Chúng ta thậm chí còn hoàn toàn có thể nâng cấp cải tiến xa hơn đoạn mã của phương pháp tự trình làng của những chiến binh. Nếu những bạn chú ý quan tâm thì trong phương pháp của lớp ChienBinh có 2 dòng lệnh trùng với những lệnh được sử dụng trong phiên bản của phương pháp trong lớp NhanVat. Chúng ta hoàn toàn có thể tận dụng phương pháp đã bị che khuất này để rút ngắn đoạn mã .

! Tận dụng lại những đoạn mã có sẵn là 1 thói quen tốt vì sẽ giúp chúng ta bảo dưỡng mã nguồn tốt hơn. Đôi khi thì lười cũng không phải là 1 tính quá xấu cool.

Chúng ta sẽ rất niềm hạnh phúc nếu hoàn toàn có thể viết như sau :

void ChienBinh::tuGioiThieu() const{
   ham_bi_che_khuat();
   // Thuc hien cac xu ly co ban cua lop NhanVat

   cout << "Toi la 1 chien binh dung manh." << endl;
   // Thuc hien xu ly dac biet cua lop ChienBinh
}

Để có thể sử dụng hàm đã bị che khuất, các bạn sẽ phải sử dụng tên đầy đủ của nó, trong trường hợp này là NhanVat::tuGioiThiet().

void ChienBinh::tuGioiThieu() const{
   NhanVat::tuGioiThiet();
   // Thuc hien cac xu ly co ban cua lop NhanVat
   cout << "Toi la 1 chien binh dung manh." << endl;
   // Thuc hien xu ly dac biet cua lop ChienBinh
}

Và tác dụng tất cả chúng ta nhận được vẫn không hề đổi khác gì cả .

Dấu :: chính là phép toán cho phép trình biên xác định hàm hay biến nào cần được sử dụng khi có nhiều sự lựa chọn

Tóm tắt bài hoc :

  • Sự kế thừa cho phép chuyên biệt hóa 1 lớp.
  • Khi 1 lớp kế thừa 1 lớp khác thì nó sẽ kế thừa tất cả các thuộc tính và phương thức của lớp đó.
  • Chúng ta có thể sử dụng sự kế thừa khi chúng ta có thể nói « vật A là 1 vật B » như « tiếng Việt là 1 ngôn ngữ ».
  • Lớp nền tảng của sự kế thừa là lớp mẹ, còn lớp kế thừa gọi là lớp con.
  • Các phương thức khởi tạo sẽ được gọi theo thứ tự xác định : bắt đầu từ lớp mẹ rồi mới tới lớp con.
  • Ngoài quyên truy cập public và private thì còn có quyền truy cập protected. Quyền truy cập này tương tự như private nhưng thoáng hơn 1 chút khi cho phép các lớp con được thao tác trực tiếp với các thành phần của lớp mẹ.
  • Nếu 2 phương thức có cùng tên trong cả lớp mẹ và lớp con thì phương thức trong lớp con sẽ chuyên biệt hơn và được sử dụng trong trường hợp mặc định.