Bài giảng Lập trình Hướng đối tượng - Chương 5: Kỹ thuật thừa kế - Lê Đức Thịnh
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: Kỹ thuật thừa kế - Lê Đức Thịnh", để 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_ky_thuat_thua_k.ppt
Nội dung text: Bài giảng Lập trình Hướng đối tượng - Chương 5: Kỹ thuật thừa kế - Lê Đức Thịnh
- LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG LÊ ĐỨC THỊNH
- Chương V: KỸ THUẬT THỪA KẾ 1. Giới thiệu chung 2. Đơn thừa kế 3. Hàm ảo và tính đa hình 4. Đa thừa kế
- 1. Giới thiệu chung ◼ Một lớp được xây dựng thừa kế một lớp khác gọi là lớp dẫn xuất (lớp con). Lớp dùng để xây dựng lớp dẫn xuất gọi là lớp cơ sở (lớp cha). ◼ Tính thừa kế: Một lớp dẫn xuất ngoài các thành phần của riêng nó, nó còn được thừa kế tất cả các thành phần của các lớp cơ sở có liên quan.
- Ví dụ Lớp cơ sở SINH VIÊN Kế thừa CHÍNH CAO TẠI HOÀN QUY ĐẲNG CHỨC CHỈNH Lớp dẫn xuất
- 2. Đơn thừa kế ◼ Cách xây dựng lớp dẫn xuất: Giả sử đã định nghĩa các lớp A và B. Để xây dựng lớp C dẫn xuất từ A và B, ta viết như sau: class C : public A, public B { private: // Các thành phần riêng protected: // Các thành phần cho phép lớp con truy xuất public: // Các thành phần công cộng } ;
- ◼ Các từ khoá quy định phạm vi truy nhập của lớp cơ sở: + Các thành phần private của lớp cở sở không cho phép truy nhập trong lớp dẫn xuất. + Các thành phần public của lớp cơ sở có thể truy nhập trong các lớp dẫn xuất. + Các thành phần khai báo là protected chỉ được mở rộng phạm vi truy nhập cho các lớp dẫn xuất trực tiếp từ lớp này.
- ◼ Thừa kế private và public: lớp C thừa kế public các lớp A và B. Nếu thay từ khoá public bằng private, thì sự thừa kế là private. ◼ Chú ý: Nếu bỏ qua không dùng từ khoá thì hiểu là private, ví dụ nếu định nghĩa: class C : public A, B { private: // Khai báo các thuộc tính public: // Các phương thức } ; ◼ thì A là lớp cơ sở public của C , còn B là lớp cơ sở private của C.
- + Các thành phần public và protected của lớp cơ sở sẽ trở thành các thành phần public và protected của lớp dẫn xuất theo kiểu dẫn xuất public. + Các thành phần public và protected của lớp cơ sở sẽ trở thành các thành phần private của lớp dẫn xuất theo kiểu dẫn xuất private. ◼ Thừa kế các thành phần dữ liệu (thuộc tính): Tập thuộc tính của lớp dẫn xuất sẽ gồm: các thuộc tính mới khai báo trong định nghĩa lớp dẫn xuất và các thuộc tính của lớp cơ sở. ◼ Tuy vậy trong lớp dẫn xuất không cho phép truy nhập đến các thuộc tính private của lớp cơ sở.
- ◼ Thừa kế phương thức: Trừ: + Hàm tạo + Hàm huỷ + Toán tử gán các phương thức (public) khác của lớp cơ sở được thừa kế trong lớp dẫn xuất.
- ◼ Cách xây dựng hàm tạo của lớp dẫn xuất: tên_lớp_con(danh sách đối) : tên_đối_tượng_thành_phần(danh sách giá trị), , tên_lớp_cơ_sở(danh sách giá trị), { // Các câu lệnh trong thân hàm tạo } ◼ Ví dụ:
- class NGUOI { private: char *ht; int ns; public: NGUOI() { ht=NULL; ns=0; } NGUOI(char *ht1, int ns1) { int n = strlen(ht1); ht = new char[n+1]; strcpy(ht,ht1); ns=ns1; }
- ~NGUOI() { if (ht!=NULL) { delete ht; ns=0; } } void in() { cout << "\nHo ten : " << ht << " nam sinh: " << ns; } } ;
- class GIAO_VIEN : public NGUOI { private: char *bomon; MON_HOC mh; public: GIAO_VIEN(): mh(),NGUOI()//Su dung ham tao khong doi { bomon=NULL; } GIAO_VIEN(char *ht1, int ns1, char *monhoc1,int st1, char *bomon1 ): NGUOI(ht1,ns1),mh(monhoc1, st1) { int n = strlen(bomon1); bomon = new char[n+1]; strcpy(bomon,bomon1); }
- ~GIAO_VIEN() { if (bomon!=NULL) delete bomon; } void in() { // Su dung phuong thuc in NGUOI::in(); cout << "\n Cong tac tai bo mon: " << bomon; mh.in(); } };
- ◼ Hàm huỷ: Khi một đối tượng của lớp dẫn xuất được giải phóng (bị huỷ), thì các đối tượng thành phần và các đối tượng thừa kế từ các lớp cơ sở cũng bị giải phóng theo. Do đó các hàm huỷ tương ứng sẽ được gọi đến. ◼ Như vậy khi xây dựng hàm huỷ của lớp dẫn xuất, chúng ta không cần để ý đến các đối tượng thành phần và các thuộc tính thừa kế từ các lớp cơ sở.
- 3. Hàm ảo và tính đa hình 3.1 Đặt vấn đề ◼ Con trỏ có thể nhận địa chỉ của bất kỳ đối tượng con cháu nào. ◼ Lời gọi đến một phương thức sẽ gọi đến ph/th tương ứng với kiểu con trỏ chứ không t/ư với ĐT đang được trỏ đến. ◼ Để gọi được ph/th t/ư với ĐT được trỏ đến ta dùng khái niệm “hàm ảo”.
- class point //VD 1 { virtual void display() { cout<<“Toa do: “<< x<< “, “<< y << endl; } }; class coloredpoint : public point { void display() { point::display(); cout<< “Mau: “<< color<< endl; } };
- void main() { point p(10, 20); coloredpoint pc(2, 3, 5); point *ptr = &p; ptr->display(); //gọi đến point::display() ptr= &pc; ptr->display(); //gọi đến coloredpoint::display() } ◼ Hàm point::display() là hàm ảo. ◼ Hàm display() không bắt buộc phải định nghĩa lại. ◼ Trong main(), lời gọi ptr->display() sẽ gọi đến point::display() hay coloredpoint::display() tùy thuộc vào ĐT ptr đang trỏ đến.
- class point //VD 2 { virtual void Identifier() { cout<< “Diem khong mau \n”; } void display() { cout<<“Toa do: “<< x<< “, “<< y << endl; Identifier(); } }; class coloredpoint : public point { void Identifier() { cout<< “Mau: “<< color<< endl; } };
- void main() { point p(10, 20); coloredpoint pc(2, 3, 5); point *ptr = &p; ptr->display(); //display() gọi đến point::Identifier() ptr= &pc; ptr->display(); //display() gọi đến coloredpoint::Identifier() } ◼ Hàm point::Identifier() là hàm ảo. Identifier() được định nghĩa lại trong lớp coloredpoint. ◼ Trong main(), lời gọi ptr->display(), display() sẽ gọi đến point::Identifier() hay coloredpoint::Identifier() tương ứng ĐT ptr đang trỏ đến là point hay coloredpoint.
- 3.2 Tổng quan về hàm ảo 3.2.1 Phạm vi khai báo virtual ◼ Khi hàm f() được khai báo virtual ở trong lớp A, nó được xem như thể hiện của sự ghép kiểu động trong lớp A và trong tất cả các lớp dẫn xuất từ A. 3.2.2 Không nhất thiết phải định nghĩa lại hàm virtual 3.2.3 Định nghĩa chồng hàm ảo 3.2.4 Khai báo hàm ảo ở một lớp bất kỳ trong sơ đồ thừa kế 3.2.5 Hàm hủy bỏ ảo
- class point { virtual ~point() { cout<< “point::~point() \n”; } }; class threedimpoint : public point { ~threedimpoint() { cout<< “threedimpoint::~threedimpoint() \n”; } }; class coloredthreedimpoint : public threedimpoint { ~coloredthreedimpoint() { cout<< “coloredthreedimpoint::~coloredthreedimpoint() \n”; } };
- void main() { point *p0= new point(10, 20); point *p1= new threedimpoint(2, 3, 5); point *p2= new coloredthreedimpoint(2, 3, 4 10); delete p0; //gọi tới ~point delete p1; //gọi tới ~threedimpoint delete p2; //gọi tới ~coloredthreedimpoint } ◼ Hàm point::~point() là hàm ảo. ◼ Trong main(), câu lệnh delete contrỏlớppoint sẽ gọi đến hàm hủy tương ứng với của ĐT mà con trỏ đang trỏ đến.
- 3.3 Lớp trừu tượng và hàm ảo thuần túy ◼ Hàm ảo thuần túy là hàm không có phần định nghĩa. class mere { public: virtual display()= 0; //hàm ảo thuần túy }; ◼ Lớp có hàm ảo thuần túy được gọi là lớp trừu tượng. ◼ Không thể sử dụng lớp trừu tượng để khai báo các biến. ◼ Được phép khai báo con trỏ có kiểu là lớp trừu tượng. ◼ Một hàm ảo thuần túy khai báo trong lớp trừu tượng cơ sở phải được định nghĩa lại trong một lớp dẫn xuất hoặc nếu không thì phải được tiếp tục khai báo ảo trong lớp dẫn xuất.
- 4. Đa thừa kế ◼ Đa kế thừa: Tình huống một lớp kế thừa từ hai hay nhiều lớp cơ sở khác nhau được gọi là sự đa kế thừa. class A { private: int n; float a[20]; public: void nhap(); void xuat(); } ;
- class B { private: int m,n; float a[20][20]; public: void nhap(); void xuat(); } ; class D : public A, public B { private: int k; public: void nhap(); void xuat(); } ;
- D h ; // Khai báo h là đối tượng của lớp D h.nhap() ; // tương tương với h.D::nhap(); h.A::xuat(); // In giá trị các thuộc tính h.A::n và h.A::a
- ◼ Một lớp cơ sở xuất hiện nhiều lần trong lớp dẫn xuất class A { public: int a; } ; class B : public A { public: int b; } ; class C : public A { public: int c; } ;
- class D : public B , public C { public: int d; } ; void main() { D h ; h.d = 4 ; // tốt h.c = 3 ; // tốt h.b = 2 ; // tốt h.a = 1 ; // lỗi }
- ◼ C++ không thể nhận biết được thuộc tính a thừa kế thông qua B hay thông qua C và nó sẽ đưa ra thông báo lỗi sau: Member is ambiguous: ‘A::a’ and ‘A::a’ ◼ Các lớp cơ sở ảo: Các lớp cơ sở ảo (virtual) sẽ được kết hợp để tạo một lớp cơ sở duy nhất cho bất kỳ lớp nào dẫn xuất từ chúng. class B : virtual public A { public: int b; } ; class C : virtual public A { public: int c; } ;
- ◼ Khi nào cần xây dựng toán tử gán: Khi lớp dẫn xuất có các thuộc tính là con trỏ, thì nhất thiết không được dùng toán tử gán mặc định, mà phải xây dựng cho lớp dẫn xuất một toán tử gán. ◼ Cách xây dựng toán tử gán cho lớp dẫn xuất + Trước hết cần xây dựng toán tử gán cho các lớp cơ sở + Vấn đề mấu chốt là: Khi xây dựng toán tử gán cho lớp dẫn xuất thì làm thế nào để sử dụng được các toán tử gán của lớp cơ sở. Cách giải quyết như sau:
- class A { A & operator=(A& h) { //các câu lệnh để thực hiện gán trong A } // Phương thức nhận địa chỉ đối tượng ẩn của A A* get_A() { return this; } } ;
- class B : public A { B & operator=(B& h) { A *u1, *u2; u1 = this->get_A(); u2 = h.get_A(); *u1 = *u2 ; // Sử dụng phép gán trong A để gán các // thuộc tính mà B kế thừa từ A //Các câu lệnh thực hiện gán các thuộc tính riêng của B } } ;
- ◼ Khi nào cần xây dựng hàm tạo sao chép: Khi lớp dẫn xuất có các thuộc tính (kể cả thuộc tính thừa kế từ các lớp cơ sở) là con trỏ, thì nhất thiết không được dùng hàm tạo sao chép mặc định, mà phải xây dựng cho lớp dẫn xuất một hàm tạo sao chép. ◼ Cách xây dựng hàm tạo sao chép cho lớp dẫn xuất + Trước hết cần xây dựng toán tử gán cho lớp dẫn xuất. + Sau đó xây dựng hàm tạo sao chép cho lớp dẫn xuất theo mẫu: Tên_lớp_dẫn_xuất (Tên_lớp_dẫn_xuất &h ) { *this = h ; }
- . Bài tập ◼ Bài tập chương 4 sách Nguyễn Thanh Thủy, Bài tập lập trình hướng đối tượng với C++, NXB Khoa học kỹ thuật Hà Nội 2001.