Sự kế thừa là một đặc điểm của ngôn ngữ dùng để biểu diễn mối quan hệ đặc biệt giữa các lớp. Các lớp được trừu tượng hóa và tổ chức thành một sơ đồ phân cấp lớp.
Kế thừa là một cơ chế trừu tượng hóa. Thủ tục và hàm là cơ chế trừu tượng hóa cho giải thuật, record và struct là trừu tượng hóa cho dữ liệu. Khái niệm lớp trong C++, kết hợp dữ liệu và thủ tục để được kiểu dữ liệu trừu tượng với giao diện độc lập với cài đặt và cho người sử dụng cảm giác thoải mái như kiểu dữ liệu có sẵn
Sự kế thừa là một mức cao hơn của trừu tượng hóa, cung cấp một cơ chế gom chung các lớp có liên quan với nhau thành một mức khái quát hóa đặc trưng cho toàn bộ các lớp nói trên. Các lớp với các đặc điểm tương tự nhau có thể được tổ chức thành một sơ đồ phân cấp kế thừa. Lớp ở trên cùng là trừu tượng hóa của toàn bộ các lớp ỏ bên dưới nó.
109 trang |
Chia sẻ: Mr Hưng | Lượt xem: 1073 | Lượt tải: 0
Bạn đang xem trước 20 trang nội dung tài liệu Kĩ thuật lập trình - Sự kế thừa, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Sự kế thừa4.1 Mở đầuSự kế thừa là một đặc điểm của ngôn ngữ dùng để biểu diễn mối quan hệ đặc biệt giữa các lớp. Các lớp được trừu tượng hóa và tổ chức thành một sơ đồ phân cấp lớp.Kế thừa là một cơ chế trừu tượng hóa. Thủ tục và hàm là cơ chế trừu tượng hóa cho giải thuật, record và struct là trừu tượng hóa cho dữ liệu. Khái niệm lớp trong C++, kết hợp dữ liệu và thủ tục để được kiểu dữ liệu trừu tượng với giao diện độc lập với cài đặt và cho người sử dụng cảm giác thoải mái như kiểu dữ liệu có sẵnSự kế thừa là một mức cao hơn của trừu tượng hóa, cung cấp một cơ chế gom chung các lớp có liên quan với nhau thành một mức khái quát hóa đặc trưng cho toàn bộ các lớp nói trên. Các lớp với các đặc điểm tương tự nhau có thể được tổ chức thành một sơ đồ phân cấp kế thừa. Lớp ở trên cùng là trừu tượng hóa của toàn bộ các lớp ỏ bên dưới nó.Mở đầuQuan hệ “là 1”: Kế thừa được sử dụng thông dụng nhất để biểu diễn quan hệ “là 1”.Một sinh viên là một ngườiMột hình tròn là một hình ellipseMột tam giác là một đa giácKế thừa tạo khả năng xây dựng lớp mới từ lớp đã có, trong đó hàm thành phần được thừa hưởng từ lớp cha. Trong C++, kế thừa còn định nghĩa sự tương thích, nhờ đó ta có cơ chế chuyển kiểu tự động.Kế thừa vừa có khả năng tạo cơ chế khái quát hoá vừa có khả năng chuyên biệt hoá.Kế thừa cho phép tổ chức các lớp chia sẻ mã chương trình chung nhờ vậy có thể dễ dàng sửa chữa, nâng cấp hệ thống.Mở đầuKế thừa thường được dùng theo hai cách:Để phản ánh mối quan hệ giữa các lớp. Là công cụ để tổ chức và phân cấp lớp dựa vào sự chuyên biệt hóa, trong đó một vài hàm thành phần của lớp con là phiên bản hoàn thiện hoặc đặc biệt hoá của phiên bản ở lớp cha. Trong C++ mối quan hệ này thường được cài đặt sử dụng:Kế thừa public.Hàm thành phần là phương thức ảoĐể phản ánh sự chia sẻ mã chương trình giữa các lớp không có quan hệ về mặt ngữ nghĩa nhưng có thể có tổ chức dữ liệu và mã chương trình tương tự nhau. Trong C++, cơ chế chia sẻ mã này thường được cài đặt dùng: Kế thừa private.Hàm thành phần không là phương thức ảo.4.2 Kế thừa đơnKế thừa có thể được thực hiện để thể hiện mối quan hệ 'là một'.Xét hai khái niệm người và sinh viên với mối quan hệ tự nhiên: một 'sinh viên' là một 'người'. Trong C++, ta có thể biểu diễn khái niệm trên, một sinh viên là một người có thêm một số thông tin và một số thao tác (riêng biệt của sinh viên).Ta tổ chức lớp sinh viên kế thừa từ lớp người. Lớp người được gọi là lớp cha (superclass) hay lớp cơ sở (base class). Lớp sinh viên được gọi là lớp con (subclass) hay lớp dẫn xuất (derived class). Kế thừa đơnclass Nguoi {friend class SinhVien; char *HoTen; int NamSinh;public: Nguoi(char *ht, int ns):NamSinh(ns) {HoTen=strdup(ht);} ~Nguoi() {delete [] HoTen;} void An() const { coutlast; } Item* operator()() { Item *ret = ce ? &((ce = ce->next)->e) : NULL; if (ce == cs->last) ce = NULL; return ret; }};Kế thừa privateclass Stack:private List {public: Stack():List() {} bool Empty() const {return List::Empty();} bool Push(Item x) {Insert(x); return true;} bool Pop(Item *px);};inline bool Stack::Pop(Item *px) { Item *p = GetFirst(); if (!p) return false; *px = *p; return true;}Kế thừa privateclass Set:private List {public: Set():List(){} void Add(Item x) { if (!List::IsMember(x)) Insert(x); } bool Empty() const {return List::Empty();} bool IsMember(Item x) const { return List::IsMember(x); } int Count() const {return List::Count();} void View() const {List::View();}};Caùc lôùp Stack vaø Set taän duïng ñöôïc caáu truùc döõ lieäu vaø chi tieát caøi ñaët cuûa lôùp List, nhöng khoâng bò trôû thaønh List vì söû duïng keá thöøa private. Caùc thao taùc cuûa List khoâng bò keá thöøa xuoáng lôùp con Stack vaø Set.Kế thừa privateMột số hàm thành phần của lớp cơ sở List có thể cần thiết ở lớp con như hàm Empty trong lớp Stack, hàm Empty, IsMember, Count trong lớp Set Ta định nghĩa lại những hàm này bằng cách gọi lại phiên bản trong lớp List.Một cách thay thế việc viết lại hàm như trên là khai báo lại các danh hiệu này trong phần public của lớp con. class Stack:private List{public: Stack():List() {} bool Push(Item x) {Insert(x); return true;} bool Pop(Item *px); List::Empty; // access specifier};Khai báo trên chỉ rõ rằng danh hiệu Empty trong lớp List trở thành public trong lớp Stack. Kế thừa privateTa có thể làm tương tự cho lớp Set.class Set:private List {public: Set():List(){} void Add(Item x) { if (!List::IsMember(x)) Insert(x); } List::Empty(); // access specifier List::IsMember; List::Count; List::View;};Ta dùng kế thừa private trong các trường hợp muốn tận dụng mã chương trình chung và có thể muốn kế thừa một phần nhưng không phải tất cả các thao tác của lớp cơ sở. Sau đây là một vài ví dụ:Kế thừa privateclass Diem { double x,y;public: Diem(double xx = 0, double yy = 0):x(xx),y(yy){} void TinhTien(double dx, double dy); friend Diem operator + (Diem d); friend Diem operator - (Diem d); //...};class HinhTron: { double r;public: HinhTron(double tx, double ty, double rr):Diem(tx,ty),r(rr){} void Ve(int color) const; void TinhTien(double dx, double dy) const;};class MangDiem { int n, size; Diem *pd; void Init(int nn, int sz, Diem *data = NULL);public: MangDiem(){Init(0,10);} MangDiem(int nn, int sz, Diem *data = NULL){Init(nn,sz,data);} MangDiem(const MangDiem &md); ~MangDiem() {delete [] pd;} MangDiem &operator = (const MangDiem &d); void Them(Diem d); MangDiem operator + (Diem d) const; MangDiem& operator += (Diem d); MangDiem operator + (const MangDiem md) const; MangDiem& operator += (MangDiem md) const; MangDiem& operator next = last; } ~List() { CleanUp(); } bool Empty() const {return last == NULL;} bool IsMember(Item x) const; int Count() const; void View() const;};Không dùng kế thừa privateclass Stack { List l;public: Stack():l() {} bool Push(Item x) {l.Insert(x); return true;} bool Pop(Item *px); bool Empty() const {return l.Empty();}};inline bool Stack::Pop(Item *px) { Item *p = l.GetFirst(); if (!p) return false; *px = *p; return true;}Không dùng kế thừa privateclass Set { List l;public: Set():l(){} void Add(Item x) {if (!l.IsMember(x)) Insert(x);} bool Empty() const {return l.Empty();} bool IsMember(Item x) const {return l.IsMember(x);} int Count() const {return l.Count();} void View() const {l.View();}};Không dùng kế thừa privateclass HinhTron { double r; Diem tam;public: HinhTron(double tx, double ty, double rr): tam(tx,ty),r(rr){} void Ve(int color) const; void TinhTien(double dx, double dy) const;};Không dùng kế thừa privateDiem dd[] = {Diem(100,100), Diem(200,200), Diem(200,50)};class DaGiac { MangDiem md;public: DaGiac():md(3,3,dd){} DaGiac(int sd, Diem *pd):md(sd,sd,pd){} DaGiac(const DaGiac &d):md(d.md){} DaGiac& operator = (const DaGiac &d); void Ve(int color) const; void Quay(Diem Tam, double goc); void TinhTien(double dx, double dy) const; Diem TrongTam() const; //...};Phương thức thiết lập và huỷ bỏPhương thức thiết lập và huỷ bỏ là các hàm thành phần đặc biệt dùng để tự động khởi động đối tượng khi nó được tạo ra và tự động dọn dẹp đối tượng khi nó bị hủy đi. Một đối tượng thuộc lớp con có chứa các thành phần dữ liệu của các lớp cơ sở. Có thể xem lớp con có các thành phần ngầm định ứng với các lớp cơ sở. Vì vậy khi một đố tượng thuộc lớp con được tạo ra, các thành phần cơ sở cũng được tạo ra, nghĩa là phương thức thiết lập của các lớp cơ sở phải được gọi.Trình biên dịch tự động gọi phương thức thiết lập của các lớp cơ sở cho các đối tượng (cơ sở) nhúng vào đối tượng đạng được tạo ra. Đối với phương thức thiết lập của một lớp con, công việc đầu tiên là gọi phương thức thiết lập của các lớp cơ sở.Phương thức thiết lập và huỷ bỏNếu mọi phương thức thiết lập của lớp cơ sở đều đòi hỏi phải cung cấp tham số thì lớp con bắt buộc phải có phương thức thiết lập để cung cấp các tham số đó.class Diem { double x,y;public: Diem(double x, double y):x(xx),y(yy){} //...};class HinhTron:Diem{ double r;public: void Ve(int color) const;}; HinhTron t; // SAICung cấp tham số cho phương thức thiết lập của lớp chaTrong trường hợp đó, lớp con bắt buộc phải có phương thức thiết lập để cung cấp tham số cho phương thức thiết lập của lớp cơ sở. Cú pháp để gọi phương thức thiết lập của lớp cơ sở tương tự như cú pháp thiết lập đối tượng thành phần, bản thân tên lớp cơ sở được quan điểm như đối tượng thành phần nhúng vào lớp con.class HinhTron:Diem { double r;public: HinhTron(double tx, double ty, double rr):Diem(tx,ty),r(rr){} void Ve(int color) const; void TinhTien(double dx, double dy) const;};HinhTron t(200,200,50); // DungPhương thức thiết lập và huỷ bỏclass Nguoi {protected: char *HoTen; int NamSinh;public: Nguoi(char *ht, int ns):NamSinh(ns) { HoTen = strdup(ht); }//...};class SinhVien : public Nguoi { char *MaSo;public: SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns) { MaSo = strdup(ms); } void Xuat() const;};Phương thức thiết lập và huỷ bỏSau khi phương thức thiết lập của các lớp cơ sở được gọi, mã chương trình trong bản thân phương thức của lớp con sẽ được thực hiện. Nội dung của phương thức thiết lập ở lớp con chỉ nên thao tác trên dữ liệu của riêng lớp con, việc khởi động dữ liệu thuộc lớp cha do phương thức thiết lập ở lớp cha đảm nhiệm với các tham số cung cấp bởi lớp con.class SinhVien : public Nguoi{ char *MaSo;public: SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns) { MaSo = strdup(ms); } void Xuat() const;};Phương thức thiết lập và huỷ bỏTa có thể khởi động các thành phần của lớp cha bên trong phương thức thiết lập của lớp con. Trong trường hợp này đối tượng thuộc lớp cha phải có khả năng tự khởi động:class Complex {protected: double re, im;public: Complex(double r = 0, double i = 0):re(r), im(i){} Complex operator +(Complex b); Complex operator -(Complex b); Complex operator *(Complex b); Complex operator /(Complex b); double Norm() const {return sqrt(re*re + im*im);}};Phương thức thiết lập và huỷ bỏHai cách thiết lập đối tượng thuộc lớp con sau đây tương đương:// Cach 1class Imag: public Complex{public: Imag(double i = 0):Complex(0, i){} //...};// Cach 2class Imag: public Complex{public: Imag(double i = 0){re = 0; im = i;){} //...};Phương thức thiết lập và huỷ bỏCách tiếp cận thứ hai tốt hơnclass Nguoi {protected: char *HoTen; int NamSinh;public: Nguoi(char *ht = "Ng Van A", int ns = 1980) :NamSinh(ns) {HoTen = strdup(ht);} ~Nguoi() {delete [] HoTen;} //...};Phương thức thiết lập và huỷ bỏ// Cach 1class SinhVien : public Nguoi { char *MaSo;public: SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns) { MaSo = strdup(ms); } void Xuat() const;};// Cach 2class SinhVien : public Nguoi { char *MaSo;public: SinhVien(char *ht, char *ms, int ns) { HoTen = strdup(ht); MaSo = strdup(ms); NamSinh = ns; }};Phương thức thiết lập và huỷ bỏPhương thức thiết lập sao chép là cần thiết trong trường hợp đối tượng có nhu cầu cấp phát tài nguyên.class Nguoi {protected: char *HoTen; int NamSinh;public: Nguoi(char *ht, int ns):NamSinh(ns) { HoTen = strdup(ht); } Nguoi(const Nguoi &n):NamSinh(n.NamSinh) { HoTen = strdup(n.HoTen); } ~Nguoi() { delete [] HoTen;}//...};Phương thức thiết lập và huỷ bỏỞ lớp con liệu có cần phương thức thiết lập sao chép ?// Khong dung pttl sao chep : D hay Sclass SinhVien : public Nguoi { char *MaSo;public: SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns) { MaSo = strdup(ms);} ~SinhVien() {delete [] MaSo;}//...};void main() { SinhVien s1("Vo Vien Sinh", "200002541",1984); SinhVien s2(s1);}Phương thức thiết lập và huỷ bỏỞ lớp con liệu có cần phương thức thiết lập sao chép ?// Lop con co pttl sao chep : co can thiet ? class SinhVien : public Nguoi { char *MaSo;public: SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns) { MaSo = strdup(ms);} SinhVien(const SinhVien &s) : Nguoi(s) { MaSo = strdup(s.MaSo);}//...};void main() { SinhVien s1("Vo Vien Sinh", "200002541",1984); SinhVien s2(s1);}Phương thức thiết lập và huỷ bỏKhi một đối tượng bị huỷ đi, phương thức huỷ bỏ của nó sẽ được gọi, sau đó phương thức huỷ bỏ của các lớp cơ sở sẽ được gọi một cách tự động. Vì vậy lớp con không cần và cũng không được thực hiện các thao tác dọn dẹp cho các thành phần thuộc lớp cha.class SinhVien : public Nguoi { char *MaSo;public: SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns) { MaSo = strdup(ms);} SinhVien(const SinhVien &s) : Nguoi(s) { MaSo = strdup(s.MaSo);} ~SinhVien() {delete [] MaSo;}//...}; Con trỏ và kế thừaCon trỏ trong kế thừa hoạt động theo nguyên tắc sau:Con trỏ đến đối tượng thuộc lớp cơ sở thì có thể trỏ đến các đối tượng thuộc lớp con.Điều ngược lại không đúng, con trỏ đến đối tượng thuộc lớp con thì không thể trỏ đến các đối tượng thuộc lớp cơ sở.Ta có thể ép kiểu để con trỏ đến đối tượng thuộc lớp con có thể trỏ đến đối tượng thuộc lớp cơ sở. Tuy nhiên thao tác này có thể nguy hiểm.Sử dụng ép kiểu đúng cách có thể giải quyết bài toán quản lý một danh sách các đối tượng khác kiểu.Con trỏ và kế thừavoid main() { clrscr(); Nguoi n("Nguyen Van Nhan", 1970); SinhVien s("Vo Vien Sinh", "200002541",1984); Nguoi *pn; SinhVien *ps; pn = &n; ps = &s; pn = &s; ps = &n; // Sai ps = pn; // Sai ps = (SinhVien *)&n; // Sai logic ps = (SinhVien *)pn;}Phương thức ảo và tính đa hình Bài toán quản lý một danh sách các đối tượng khác kiểuGiả sử ta cần quản lý một danh sách các đối tượng có kiểu có thể khác nhau, ta cần giải quyết hai vấn đề: Cách lưu trữ và thao tác xử lý.Xét trường hợp cụ thể, các đối tượng có thể là người, sinh viên hoặc công nhân.Về lưu trữ: Ta có thể dùng union, trong trường hợp này mỗi đối tượng phải có kích thước chứa được đối tượng có kích thước lớn nhất. Điều này gây lãng phí không gian lưu trữ. Một cách thay thế là lưu trữ đối tượng bằng đúng kích thước của nó và dùng một danh sách (mảng, dslk,...) các con trỏ để quản lý các đối tượng. Về thao tác, phải thoả yêu cầu đa hình: Thao tác có hoạt động khác nhau ứng với các loại đối tượng khác nhau. Có hai cách giải quyết là vùng chọn kiểu và phương thức ảo.Dùng vùng chọn kiểuVề lưu trữ: Ta sẽ dùng một mảng các con trỏ đến lớp cơ sở để có thể trỏ đến các đối tượng thuộc lớp con. Xét lớp Người và các lớp kế thừa sinh viên và công nhân. Thao tác ta quan tâm là xuat. Ta cần bảo đảm thao tác xuất áp dụng cho lớp sinh viên và lớp công nhân khác nhau.Dùng vùng chọn kiểuclass Nguoi {protected: char *HoTen; int NamSinh;public: Nguoi(char *ht, int ns):NamSinh(ns) {HoTen = strdup(ht);} ~Nguoi() {delete [] HoTen;} void An() const { cout Xuat(); cout pl) { case Nguoi::SV: ((SinhVien *)an[i])->Xuat(); break; case Nguoi::CN: ((CongNhan *)an[i])->Xuat(); break; default: an[i]->Xuat(); break; } cout Xuat(); // Mong muon: goi Xuat cua lop sinh vien, // thuc te: goi Xuat cua lop NguoiPhương thức ảo cho phép giải quyết vấn đề. Ta qui định một hàm thành phần là phương thức ảo bằng cách thêm từ khoá virtual vào trước khai báo hàm.Trong ví dụ trên, ta thêm từ khoá virtual vào trước khai báo của hàm xuat.Phương thức ảoclass Nguoi {protected: char *HoTen; int NamSinh;public: Nguoi(char *ht, int ns):NamSinh(ns) {HoTen = strdup(ht);} ~Nguoi() {delete [] HoTen;} void An() const { cout Xuat(); cout Xuat(); // Goi thao tac xuat cua lop Sinh vienCon troû pn thuoäc lôùp Nguoi nhöng troû ñeán ñoái töôïng sinh vieân, vì vaäy pn->Xuat() thöïc hieän thao taùc xuaát cuûa lôùp sinh vieân.Trôû laïi ví duï treân, khi i a[i] laàn löôït troû ñeán caùc ñoái töôïng thuoäc caùc loaïi khaùc nhau, thao taùc töông öùng vôùi lôùp seõ ñöôïc goïi.Duøng phöông thöùc aûo khaéc phuïc ñöôïc caùc nhöôïc ñieåm cuûa caùch tieáp caän duøng vuøng choïn kieåu:Thao taùc ñôn giaûn khoâng phaûi duøng switch/case vì vaäy khoù sai, deã söûa.Thêm lớp con mớiDùng phương thức ảo, ta dễ dàng nâng cấp sửa chữa. Việc thêm một loại đối tượng mới rất đơn giản, ta không cần phải sửa đổi thao tác xử lý (hàm XuatDs). Qui trình thêm chỉ là xây dựng lớp con kế thừa từ lớp cơ sở hoặc các lớp con đã có và định nghĩa lại phương thức (ảo) ở lớp mới tạo nếu cầnclass CaSi : public Nguoi{protected: double CatXe;public: CaSi(char *ht, double cx, int ns) : Nguoi(ht,ns), CatXe(cx) {} void Xuat() const { cout Xuat(); cout Draw();}Phương thức thuần ảo và lớp cơ sở trừu tượngLớp cơ sở trừu tượng là lớp cơ sở không có đối tượng nào thuộc chính nó. Một đối tượng thuộc lớp cơ sở trừu tượng phải thuộc một trong các lớp con.Xét các lớp Circle, Rectangle, Square kế thừa từ lớp ShapeTrong ví dụ trên, các hàm trong lớp Shape có nội dung nhưng nội dung không có ý nghĩa. Đồng thời ta luôn luôn có thể tạo được đối tượng thuộc lớp Shape, điều này không đúng với tư tưởng của phương pháp luận hướng đối tượng. Ta có thể thay thế cho nội dung không có ý nghĩa bằng phương thức ảo thuần tuý. Phương thức ảo thuần tuý là phương thức ảo không có nội dung.Phương thức ảo thuần tuý và lớp cơ sở trừu tượngKhi lớp có phương thức ảo thuần tuý, lớp trở thành lớp cơ sở trừu tượng. Ta không thể tạo đối tượng thuộc lớp cơ sở thuần tuý.Ta có thể định nghĩa phương thức ảo thuần tuý, nhưng chỉ có các đối tượng thuộc lớp con có thể gọi nó. Trong ví dụ trên, các hàm thành phần trong lớp Shape là phương thức ảo thuần tuý. Nó bảo đảm không thể tạo được đối tượng thuộc lớp Shape. Ví dụ trên cũng định nghĩa nội dung cho phương thức ảo thuần tuý, nhưng chỉ có các đối tượng thuộc lớp con có thể gọi.Phương thức ảo thuần tuý có ý nghĩa cho việc tổ chức sơ đồ phân cấp các lớp, nó đóng vai trò chừa sẵn chỗ trống cho các lớp con điền vào với phiên bản phù hợp.Phương thức ảo thuần tuý và lớp cơ sở trừu tượngBản thân các lớp con của lớp cơ sở trừu tượng cũng có thể là lớp cơ sở trừu tượng
Các file đính kèm theo tài liệu này:
- 4_inheritant_5666.ppt