Bài giảng Lập trình hướng đối tượng - Chương 5: Tính kế thừa - Trần Minh Thái
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình hướng đối tượng - Chương 5: Tính kế thừa - Trần Minh Thái", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
Tài liệu đính kèm:
- bai_giang_lap_trinh_huong_doi_tuong_chuong_5_tinh_ke_thua_tr.pptx
Nội dung text: Bài giảng Lập trình hướng đối tượng - Chương 5: Tính kế thừa - Trần Minh Thái
- Chương 5. Tính kế thừa (Inheritance) TRẦN MINH THÁI Email: minhthai@itc.edu.vn Website: www.minhthai.edu.vn Cập nhật: 10 tháng 04 năm 2015
- Nội dung #2 1. Giới thiệu 2. Khái niệm kế thừa 3. Đơn kế thừa 4. Đa kế thừa 5. Lớp cơ sở ảo
- Giới thiệu [1/10] #3 Ngoài việc nhóm các đối tượng có cùng tập thuộc tính/hành vi lại với nhau, con người thường nhóm các đối tượng có cùng một số thuộc tính/ hành vi ▪ Ví dụ: nhóm tất cả xe chạy bằng động cơ thành một nhóm, rồi phân thành các nhóm nhỏ hơn tuỳ theo loại xe (xe ô tô, xe tải, )
- Giới thiệu [2/10] #4 Mỗi nhóm con là một lớp các đối tượng tương tự, nhưng giữa các nhóm con có chung một số đặc điểm Quan hệ giữa các nhóm con với nhóm lớn được gọi là quan hệ “là một” (is-a)
- Giới thiệu [3/10] #5 Ví dụ: o Một cái xe ô tô “là một” xe động cơ o Một cái xe tải “là một” xe động cơ o Một cái xe máy “là một” xe động cơ → Dùng cấu trúc hướng đối tượng để định nghĩa quan hệ “là một”
- Giới thiệu [4/10] #6 Các đối tượng được nhóm lại thành một lớp thì có cùng tập thuộc tính và hành vi ▪ Mọi đối tượng xe động cơ có cùng tập thuộc tính và hành vi → Mọi đối tượng xe tải có cùng tập thuộc tính và hành vi
- Giới thiệu [5/10] #7 Mối liên kết giữa các lớp trong quan hệ “là một” xuất phát từ thực tế rằng các lớp con cũng có mọi thuộc tính/ hành vi của lớp cha, và cộng thêm các thuộc tính/ hành vi khác
- Giới thiệu [6/10] #8 Lớp cha – superclass (hoặc lớp cơ sở - base class) ▪ Lớp tổng quát hơn trong mối quan hệ “là một” ▪ Các đối tượng thuộc lớp cha có cùng tập thuộc tính và hành vi
- Giới thiệu [7/10] #9 Lớp con – subclass (hoặc lớp dẫn xuất – derived class) ▪ Lớp cụ thể hơn trong một quan hệ “là một” ▪ Các đối tượng thuộc lớp con có cùng tập thuộc tính và hành vi (do kế thừa từ lớp cha), kèm thêm tập thuộc tính và hành vi của riêng lớp con
- Giới thiệu [8/10] #10 Quan hệ “là một” còn gọi là sự kế thừa (inheritance) Ta nói rằng lớp con “kế thừa từ” lớp cha, hoặc lớp con “được dẫn xuất từ” lớp cha → Kế thừa là quá trình tạo nên lớp mới bằng cách dẫn xuất từ lớp cũ
- Giới thiệu [9/10] #11 Ưu điểm của việc kế thừa ▪ Tiết kiệm thời gian và công sức ▪ Tái sử dụng lại những lớp có sẵn ▪ Giảm lượng code phải thiết kế, viết, kiểm tra ▪ Tránh trùng lắp code ▪ Rút ngắn thời gian giúp LTV tập trung vào mục tiêu ▪ Giúp phân loại và thiết kế lớp dễ dàng, dễ quản lý
- Giới thiệu [10/10] #12 Phân loại ▪ Đơn kế thừa (single inheritance): chỉ có một lớp cha ▪ Đa kế thừa (multiple inheritance): có nhiều lớp cha
- Sơ đồ quan hệ đối tượng [1/3] (Object Relationship Diagram – ORD) #13 Thể hiện ▪ Sự khác nhau giữa lớp cơ sở và lớp dẫn xuất ▪ Sự khác nhau giữa các lớp dẫn xuất
- Sơ đồ quan hệ đối tượng [2/3] #14 Biểu diễn các thành phần: ▪ private: thêm dấu trừ phía trước tên ▪ public: thêm dấu cộng phía trước tên
- Sơ đồ quan hệ đối tượng [3/3] #15 Đối với những lớp dẫn xuất, chỉ cần liệt kê các thuộc tính/ hành vi mà lớp cơ sở không có ▪ Đơn giản hoá sơ đồ ▪ Nhấn mạnh các điểm khác biệt
- Cây kế thừa [1/2] #16 Các quan hệ kế thừa luôn được biểu diễn với các lớp dẫn xuất đặt dưới lớp cơ sở để nhấn mạnh bản chất phả hệ của quan hệ
- Cây kế thừa [2/2] #17 Kế thừa phần lớn các thành viên dữ liệu và phương thức của lớp cơ sở (ngoại trừ constructor, destructor) Lớp dẫn xuất Có thể bổ sung thêm các thành viên dữ liệu mới và các phương thức mới Lớp cơ sở trực tiếp Class A Lớp cơ sở Class B Lớp cơ sở gián tiếp Class C
- Kế thừa vs Quan hệ khác #18 • Đây là quan hệ không dựa trên kế thừa • Mối quan hệ này gọi là quan hệ “có một” (has-a) cũng được gọi là quan hệ bao gộp (aggregation)
- Đơn kế thừa [1/2] #19 Cú pháp class derived_class_name : type_of_inheritance base_class_name { member_list }; ▪ type_of_inheritance là public, protected hoặc private. Mặc định là private. ▪ Từ khoá truy xuất của thuộc tính: public, private, protected.
- Đơn kế thừa [2/2] #20 Thuộc tính truy xuất Kiểu kế thừa thành viên của lớp public protected private cơ sở public trong lớp protected trong private trong public dẫn xuất lớp dẫn xuất lớp dẫn xuất protected trong protected trong private trong protected lớp dẫn xuất lớp dẫn xuất lớp dẫn xuất Dấu trong lớp Dấu trong lớp dẫn Dấu trong lớp private dẫn xuất xuất dẫn xuất public protected private
- VD - đơn kế thừa [1/4] #21 class CMyPoint { protected: float x,y; public: CMyPoint(float a= 0, float b= 0); void SetPoint(float a, float b); float GetX() const { return x; } float GetY() const { return y; } void Print() const; };
- VD - đơn kế thừa [2/4] #22 CMyPoint::CMyPoint(float a, float b) { SetPoint(a, b); } void CMyPoint::SetPoint(float a, float b) { x = a; y = b; } void CMyPoint::Print() const { cout << "(" << x << ", " << y << ")" << endl; }
- VD - đơn kế thừa [3/4] class CCircle : public CMyPoint #23 { protected: float radius; public: CCircle(float r = 0.0, float a = 0, float b = 0); void SetRadius(float r); void SetCircle(float r, float a, float b); float GetRadius() const; float Area() const; void Print() const; }; CCircle::CCircle(float r, float a, float b) : CMyPoint(a, b) { SetRadius(r); } void CCircle::SetRadius(float r) { radius = r; }
- VD - đơn kế thừa [4/4] void CCircle::SetCircle(float r, float a, float b) #24 { SetPoint(a,b); //Gọi phương thức của lớp cơ sở SetRadius(r); } float CCircle::GetRadius() const { return radius; } float CCircle::Area() const { return 3.14159f * radius * radius; } void CCircle::Print() const { cout<<"[ "; CMyPoint::Print(); cout<<", " << radius <<"]"<<endl; }
- Constructor & destructor trong kế thừa [1/5] #25 Muốn chỉ định constructor cụ thể của lớp cơ sở ▪ Constructor của lớp dẫn xuất phải chứa các thông tin làm tham số cho constructor của lớp cơ sở ▪ Gọi constructor của lớp cở sở bằng cách sử dụng bộ khởi tạo trong constructor của lớp dẫn xuất (cú pháp giống bộ khởi tạo thành viên)
- Constructor & destructor trong kế thừa [2/5] #26 CCircle::CCircle(float r, float a, float b) : CMyPoint(a, b) //a, b là các tham số của constructor cơ sở { radius = r; } Thứ tự gọi constructor và destructor A B C
- Constructor & destructor trong kế thừa [3/5] #27 Khi khai báo một đối tượng của lớp dẫn xuất thì constructor của lớp cơ sở được gọi trước → đến constructor của lớp dẫn xuất Destructor thực hiện ngược lại
- Constructor & destructor trong kế thừa [4/5] #28 class A class B: public A { { public: public: A() B() { { cout << "A"<<endl; cout << "B"<<endl; } } ~A() ~B() { { cout<<"~A"<<endl; cout<<"~B"<<endl; } } }; };
- Constructor & destructor trong kế thừa [5/5] #29 class C: public B void main() { { public: B b; C() C c; { } cout << "C"<<endl; } ~C() { cout<<"~C"<<endl; } };
- Overriding [1/3] #30 Lớp dẫn xuất có thể định nghĩa lại thành viên lớp cơ sở Điều này được gọi là overriding Phiên bản được định nghĩa trong lớp dẫn xuất sẽ được chọn khi truy cập trong lớp dẫn xuất Muốn truy cập phiên bản được định nghĩa trong lớp cơ sở từ lớp dẫn xuất phải sử dụng toán tử phạm vi
- Overriding [2/3] #31 void CCircle::Print() const { cout<<"Center: "; CMyPoint::Print(); cout<<", Radius:" << radius <<endl; }
- Overriding [3/3] #32 class B: public A class A { { public: public: void Print() void Print() { { B b; cout<<"B::Print()"; cout<<"A::Print()"; b.Fun1(); cout<<endl; cout<<endl; b.Fun2(); } } b.Print(); //B::Print(); void Fun1() }; b.A::Print(); { Print(); //B::Print(); } void Fun2() { A::Print(); } };
- Sự chuyển kiểu [1/7] #33 Một đối tượng của lớp dẫn xuất có thể sử dụng với tư cách là một đối tượng của lớp cơ sở (chuyển kiểu ngầm định) nhưng ngược lại thì không CMyPoint p; CCircle c(10,20, 100); p = c; //OK Slicing p.Print(); //CMyPoint::Print(); CMyPoint p2(50, 30); c = p2; //Error
- Sự chuyển kiểu [2/7] #34 Slicing là quá trình chuyển một thực thể của lớp dẫn xuất thành một thực thể của lớp cơ sở Thực chất là cắt bớt (slice off) những thành viên dữ liệu và phương thức được định nghĩa trong lớp dẫn xuất
- Sự chuyển kiểu [3/7] #35 Một con trỏ chỉ đến một đối tượng của lớp cơ sở có thể được gán bằng địa chỉ đối tượng của lớp dẫn xuất ▪ Khi đó TBD sẽ gắn liền thao tác thực hiện thông qua biến con trỏ đó đối với thành viên của lớp cơ sở CMyPoint *p; CCircle c(10,20, 100); p = &c; //OK Upcast p->Print(); //CMyPoint::Print();
- Sự chuyển kiểu [4/7] #36 Tương tự đối với biến tham chiếu Upcast là quá trình tương tác với thực thể của lớp dẫn xuất như thể nó là thực thể của lớp cơ sở void Fun(A & a) A { } B B b; Fun(b);
- Sự chuyển kiểu [5/7] #37 Upcast thường sử dụng trong các tham số của hàm Tham số hình thức là một con trỏ/tham chiếu đến lớp cơ sở được yêu cầu nhưng tham số thực là con trỏ/tham chiếu đến lớp dẫn xuất cũng được chấp nhận
- Sự chuyển kiểu [6/7] #38 Downcast là quy trình ngược lại, đổi kiểu con trỏ/tham chiếu tới lớp cơ sở thành con trỏ/tham chiếu tới lớp dẫn xuất Downcast là quy trình rắc rối hơn và có nhiều điểm không an toàn Đây không phải là một quy trình tự động - nó luôn đòi hỏi đổi kiểu tường minh (explicit type cast)
- Sự chuyển kiểu [7/7] #39 CMyPoint p(100,50); CCircle *c; c = &p; //Error Nếu ta biết chắc chắn một con trỏ lớp cơ sở đang trỏ tới một lớp dẫn xuất, ta có thể tự đổi kiểu cho con trỏ lớp cơ sở bằng cách sử dụng chuyển kiểu tường minh CCircle *c = new CCircle(100,50,50); CMyPoint *p = c; //Upcast CCircle *c2 = static_cast (p); //Explicit downcast
- Đa kế thừa [1/6] #40 Frame Wheel Person Fish Bicycle FishPerson • Là khả năng một lớp có nhiều lớp cơ sở • Cây kế thừa phức tạp lên rất nhiều • Có thể sinh ra các vấn đề nhập nhằng (do các tên trùng nhau thành viên)
- Đa kế thừa [2/6] #41 Cú pháp class derived_class_name : type_of_inheritance base_class_name1, type_of_inheritance base_class_name2, { member_list };
- Đa kế thừa [3/6] #42 class CCircle { class CTable protected: { float radius; protected: public: float height; CCircle(float r) public: { CTable(float h) radius=r; { } height=h; } float Area() float Height() { { return radius*radius*3.14f; return height; } } }; };
- Đa kế thừa [4/6] #43 class CRoundTable: public CTable, public CCircle { private: int color; public: CRoundTable(float h, float r, int c); int Color() { return color; } }; CRoundTable::CRoundTable(float h, float r, int c):CTable(h), CCircle(r) { color=c; CRoundTable table(1, 0.5f, 5); } cout << "Thong tin ve ban:" << endl; cout << "Chieu cao:" << table.Height() << endl; cout << "Dien tich:" << table.Area() << endl; cout << "Mau: ” << table.Color << endl;
- Đa kế thừa – Constructor & destructor [5/6] #44 Các tham số của constructor của tất cả các lớp cơ sở này được khai báo trong constructor của lớp dẫn xuất và constructor cơ sở cũng phải được khởi tạo Constructor lớp cở sở xuất hiện trước sẽ thực hiện trước và cuối cùng mới tới constructor lớp dẫn xuất Đối với destructor có trình tự thực hiện theo thứ tự ngược lại
- Đa kế thừa [6/6] #45 class A class B class C: public A, public B { { { public: public: void Method(); void Method(); }; }; }; C c; c.Method();//ambiguity //A::Method() or B::Method() ??? C c; c.A::Method(); c.B::Method();
- Lớp cơ sở ảo – Virtual base class [1/4] #46 class B: public A x class A { { A public: }; int x; class C: public A }; { }; x B C x class D: public B, public C { }; D D d; d.x = 3; //ambiguity Giải pháp: Khai báo lớp A là lớp d.B::x = 5; Giải pháp tạm thời cơ sở ảo cho cả 2 lớp B và C d.C::x = 7;
- Lớp cơ sở ảo [2/4] #47 Không thể khai báo hai lần cùng một lớp trong danh sách của các lớp cơ sở cho một lớp dẫn xuất. Tuy nhiên vẫn có thể có trường hợp cùng một lớp cơ sở được đề cập nhiều hơn một lần → Điều này phát sinh lỗi vì không có cách nào để phân biệt hai lớp cơ sở gốc
- Lớp cơ sở ảo [3/4] #48 class A { Các lớp cơ sở ảo sẽ public: int x; được kết hợp tạo ra }; class B: virtual public A một lớp cơ sở duy nhất { }; cho bất kỳ lớp dẫn xuất class C: virtual public A { nào thừa hưởng từ }; class D: public B, public C chúng { }; D d; d.x = 3; //OK
- Lớp cơ sở ảo [4/4] #49 A D B C E1 E2 F
- Kết luận #50 Trong thực tế, có một loạt các vấn đề tiềm tàng liên quan đến đa kế thừa, phần lớn là do rắc rối vì nhập nhằng Có thể không bao giờ cần dùng đến đa kế thừa, nhưng cũng có những tình huống mà đa kế thừa là lời giải tốt nhất (và có thể là duy nhất) Nếu sử dụng đa kế thừa, nhất thiết phải cân nhắc về các xung đột có thể nảy sinh trong khi sử dụng các lớp có liên quan
- Q&A #51