Lập trình hướng đối tượng với C++ - Chương 5: Kỹ thuật thừa kế

 

1. Giới thiệu chung 161

2. Đơn thừa kế 165

2.1 Ví dụ minh hoạ 165

2.2 Truy nhập các thành phần của lớp cơ sở từ lớp dẫn xuất 167

2.3 Định nghĩa lại các thành phần của lớp cơ sở trong lớp dẫn xuất 168

2.4 Tính thừa kế trong lớp dẫn xuất 168

2.4.1 Sự tương thích của đối tượng thuộc lớp dẫn xuất với đối tượng thuộc lớp cơ sở 168

2.4.2 Tương thích giữa con trỏ lớp dẫn xuất và con trỏ lớp cơ sở 170

2.4.3 Tương thích giữa tham chiếu lớp dẫn xuất và tham chiếu lớp cơ sở 172

2.5 Hàm thiết lập trong lớp dẫn xuất 174

2.5.1 Hàm thiết lập trong lớp 174

2.5.2 Phân cấp lời gọi 176

2.5.3 Hàm thiết lập sao chép 177

2.6 Các kiểu dẫn xuất khác nhau. 181

2.6.1 Dẫn xuất public 182

2.6.2 Dẫn xuất private 182

2.6.3 Dẫn xuất protected 182

Bảng tổng kết các kiểu dẫn xuất 182

3. Hàm ảo và tính đa hình 183

3.1 Đặt vấn đề 183

3.2 Tổng quát về hàm ảo 190

3.2.1 Phạm vi của khai báo virtual 190

3.2.2 Không nhất thiết phải định nghĩa lại hàm virtual 194

3.2.3 Định nghĩa chồng hàm ảo 197

3.2.4 Khai báo hàm ảo ở một lớp bất kỳ trong sơ đồ thừa kế 197

3.2.5 Hàm huỷ bỏ ảo 201

3.3 Lớp trừu tượng và hàm ảo thuần tuý 204

4. Đa thừa kế 205

4.1 Đặt vấn đề 205

4.2 Lớp cơ sở ảo 210

4.3 Hàm thiết lập và huỷ bỏ - với lớp ảo 213

4.4 Danh sách móc nối các đối tượng 219

Xây dựng lớp trừu tượng 219

4.5 Tạo danh sách móc nối không đồng nhất 227

5. Tóm tắt 231

5.1 Ghi nhớ 231

5.2 Các lỗi thường gặp 232

5.3 Một số thói quen lập trình tốt 232

6. Bài tập 232

 

 

doc74 trang | Chuyên mục: C/C++ | Chia sẻ: dkS00TYs | Lượt xem: 1835 | Lượt tải: 1download
Tóm tắt nội dung Lập trình hướng đối tượng với C++ - Chương 5: Kỹ thuật thừa kế, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
h. Do vậy ta sẽ đưa vào một hàm thành phần bổ sung cho phép kiểm tra liệu đã gặp cuối danh sách hay chưa.
Theo các phân tích trên, ta sẽ dùng thêm ba hàm thành phần:
void *first();
void *prochain();
int last();
Sau đây là định nghĩa của lớp trừu tượng list:
Ví dụ 5.17
#include //để định nghĩa NULL
//class list
struct element
 { element *next;
	void *content;
 };
class list
 {
	 element *head;
	 element *current;
 public:
	 list()
	 {
	 head=NULL;current=head;
	 }
	 ~list();
	 void add(void *)
	 void first()
	 {current=head;}
	 void *prochain(){
 void *adel =NULL;
	 if (current !=NULL) {
 adel =current->content;
	 current=current->next;
	}
	 return adel;
	 }
	 int last() {return (current==NULL);}
 };
list::~list() {
 element *suiv;
 current=head;
 while(current!=NULL)
	{suiv=current->next;delete current;current=suiv;}
 }
void list::add(void *chose) {
 element *adel= new element;
 adel->next =head;
 adel->content=chose;
 head=adel;
 }
Giả sử lớp point được định nghĩa như sau:
#include 
class point
 { int x,y;
 public:
	point(int abs=0,int ord=0) {x=abs;y=ord;}
	void display() {cout <<" Toa do : "<<x<," "<<y<<"\n";}
 };
Ta định nghĩa một lớp mới list_points thừa kế từ list và point, cho phép quản lý danh sách móc nối các đối tượng point như sau:
class list_points :public list,public point
 {
 public:
	 list_points() {}
	 void display();
 };
 void list_points::display()
 {
 first();
 while (!last()) { 
 point *ptr=(point *)prochain();ptr->display();}
 }
Sau đây là chương trình hoàn chỉnh:
Ví dụ 5.18
/*list_hom.cpp
homogenous list*/
#include 
#include //de dinh nghia NULL
#include 
//class list
struct element { 
 element *next;
 void *content;
 };
class list {
	 element *head;
	 element *current;
 public:
	 list() {
	 head=NULL;current=head;
	 }
	 ~list();
	 void add(void *);
	 void first() {
 current=head;}
	 void *nextelement() { 
 void *adel =NULL;
	 if (current !=NULL) {
	 adel =current->content;
	 current=current->next;
	 }
	return adel;
	 }
	 int last() {return (current==NULL);}
 };
list::~list() {
 element *suiv;
 current=head;
 while(current!=NULL) 	{
 suiv=current->next;
 delete current;
 current=suiv;
 }
 }
void list::add(void *chose) {
 element *adel= new element;
 adel->next =head;
 adel->content=chose;
 head=adel;
 }
class point
 { int x,y;
 public:
	point (int abs=0,int ord=0){
	 x=abs;
	 y=ord;
	 }
	void display(){
	 cout<<" Toa do : "<<x<<" "<<y<<"\n";
	 }
 };
class list_points :public list,public point {
 public:
	 list_points() {}
	 void display();
 };
 void list_points::display() {
 first();
 while (!last()) { 
 point *ptr=(point *)nextelement();
 ptr->display();
 }
 }
void main() {
 clrscr();
 list_points l;
 point a(2,3),b(5,9),c(0,8);
 l.add(&a); l.display();cout<<"-------------\n";
 l.add(&b); l.display();cout<<"-------------\n";
 l.add(&c); l.display();cout<<"-------------\n";
 getch();
 }
Toa do : 2 3
-------------
 Toa do : 5 9
 Toa do : 2 3
-------------
 Toa do : 0 8
 Toa do : 5 9
 Toa do : 2 3
-------------
Tạo danh sách móc nối không đồng nhất
Phần trước đã xét ví dụ về danh sách móc nối đồng nhất. Về nguyên tắc, các phần tử trong danh sách có thể chứa các thông tin bất kỳ-danh sách không đồng nhất. Vấn đề chỉ phức tạp khi ta hiển thị các thông tin dữ liệu liên quan, vì khi đó ta hoàn toàn không biết được thông tin về kiểu đối tượng khi khai báo tham trỏ (có thể xem lại hàm display_list() để thấy rõ hơn).
Khái niệm lớp trừu tượng và hàm ảo cho phép ta xây dựng thêm một lớp dùng làm cơ sở cho các lớp có đối tượng tham gia danh sách móc nối. Trong khai báo của lớp cơ sở này, có một hàm thành phần ảo sẽ được định nghĩa lại trong các lớp dẫn xuất. Các hàm này có chức năng (trong các lớp cơ sở ) hiển thị các thông tin liên quan đến đối tượng tương ứng của một lớp.
Đến đây, nhờ khai báo một tham trỏ đến lớp cơ sở này, mọi vấn đề truy nhập đến các hàm hiển thị thông tin về từng thành phần sẽ không còn khó khăn nữa. Sau đây là một chương trình minh hoạ cho nhận xét trên. Trong chương trình này, lớp cơ sở chung cho các lớp dùng làm kiểu dữ liệu cho các thành phần của danh sách chỉ được dùng để khai báo con trỏ để gọi tới các thành phần hàm hiển thị, nên tốt nhất ta là khai báo hàm đó như là hàm ảo thuần tuý và lớp tương ứng sẽ là lớp trừu tượng.
Ví dụ 5.19
/*list_hete.cpp
heterogenous list*/
#include 
#include //chứa giá trị NULL
#include 
//lớp trừu tượng
class mere {
 public:
 virtual void display() = 0 ; //hàm ảo thuần tuý
 };
//lớp quản lý danh sách móc nối
struct element //cấu trúc một thành phần của danh sách
{
 element *next;//chỉ đến thành phần đi sau
 void *content; //chỉ đến một đối tượng bất kỳ
};
class list
{
 element *head;
 element *current;
public:
 list(){
 head = NULL;
 current=head;
 }
 ~list();
 void add(void *);//thêm một thành phần vào đầu danh sách
 void first(){
 current=head;
 }
 /*cho địa chỉ của phần tử hiện tại , xác định thành phần tiếp theo*/
 void *nextelement()	{
	void *adnext=NULL;
	if (current !=NULL)
	 {
	 adnext=current->content;
	 current=current->next;
	 }
	return adnext;
	}
 /*liệt kê tất cả các phần tử trong danh sách*/
 void display_list(); 
 int last() {return (current==NULL);}
};
list::~list() {
 element *suiv;
 current = head;
 while(current !=NULL)	{
	suiv =current->next;
	delete current;
	current=suiv;
	}
 }
void list::add(void * chose) {
 element *adel = new element;
 adel->next = head;
 adel->content = chose;
 head = adel;
 }
void list::display_list() {
 mere *ptr ;//chú ý phải là mere* chu khong phai void *
 first();//in từ đầu danh sách
 while (!last() )	{
	ptr =(mere *) nextelement();
	ptr->display();
	}
 }
//lớp điểm
class point : public mere {
 int x,y;
public:
 point(int abs =0, int ord =0) {x=abs;y=ord;}
 void display()
	{cout << "Toa do : "<<x<<" "<<y<<"\n";}
};
//lớp số phức
class complexe :public mere {
 double reel,imag;
public:
 complexe(double r=0,double i=0) {reel =r; imag=i;}
 void display(){
	cout<<" So phuc : "<<reel<<" + "<<imag<<"i\n";
	}
};
//chương trình thử
void main() {
 clrscr();
 list l;
 point a(2,3),b(5,9);
 complexe x(4.5,2.7), y(2.35,4.86);
 l.add(&a);
 l.add(&x);
 l.display_list();
 cout<<"----------------\n";
 l.add(&y);
 l.add(&b);
 l.display_list();
 getch();
}
So phuc : 4.5 + 2.7i
Toa do : 2 3
----------------
Toa do : 5 9
 So phuc : 2.35 + 4.86i
 So phuc : 4.5 + 2.7i
Toa do : 2 3
Tóm tắt
Ghi nhớ
Thừa kế nâng cao khả năng sử dụng lại của các đoạn mã chương trình.
Người lập trình có thể khai báo lớp mới thừa kế dữ liệu và hàm thành phần tử một lớp cơ sở đã được định nghĩa trước đó. Ta gọi lớp mới là lớp dẫn xuất.
Trong đơn thừa kế, một lớp chỉ có thể có một lớp cơ sở. Trong đa thừa kế cho phép một lớp là dẫn xuất của nhiều lớp.
Lớp dẫn xuất thường bổ sung thêm các thành phần dữ liệu và các hàm thành phần trong định nghĩa, ta nói lớp dẫn xuất cụ thể hơn so với lớp cơ sở và vì vậy thường mô tả một lớp các đối tượng có phạm vi hẹp hơn lớp cơ sở.
Lớp dẫn xuất không có quyền truy nhập đến các thành phần private của lớp cơ sở. Tuy nhiên lớp cơ sở có quyền truy xuất đến các thành phần công cộng và được bảo vệ (protected).
Hàm thiết lập của lớp dẫn xuất thường tự động gọi các hàm thiết lập của các lớp cơ sở để khởi tạo giá trị cho các thành phần trong lớp cơ sở.
Hàm huỷ bỏ được gọi theo thứ tự ngược lại.
Thuộc tính truy nhập protected là mức trung gian giữa thuộc tính public và private. Chỉ có các hàm thành phần và hàm bạn của lớp cơ sở và lớp dẫn xuất có quyền truy xuất đến các thành phần protected của lớp cơ sở.
Có thể định nghĩa lại các thành phần của lớp cơ sở trong lớp dẫn xuất khi thành phần đó không còn phù hợp trong lớp dẫn xuất.
Có thể gán nội dung đối tượng lớp dẫn xuất cho một đối tượng lớp cơ sở. Một con trỏ lớp dẫn xuất có thể chuyển đổi thành con trỏ lớp cơ sở.
Hàm ảo được khai báo với từ khoá virtual trong lớp cơ sở.
Các lớp dẫn xuất có thể đưa ra các cài đặt lại cho các hàm ảo của lớp cơ sở nếu muốn, trái lại chúng có thể sử dụng định nghĩa đã nêu trong lớp cơ sở.
Nếu hàm ảo được gọi bằng cách tham chiếu qua tên một đối tượng thì tham chiếu đó được xác định dựa trên lớp của đối tượng tương ứng.
Một lớp có hàm ảo không có định nghĩa (hàm ảo thuần tuý) được gọi là lớp trừu tượng. Các lớp trừu tượng không thể dùng để khai báo các đối tượng nhưng có thể khai báo con trỏ có kiểu lớp trừu tượng.
Tính đa hình là khả năng các đối tượng của các lớp khác nhau có quan hệ thừa kế phản ứng khác nhau đối với cùng một lời gọi hàm thành phần.
Tính đa hình được cài đặt dựa trên hàm ảo.
Khi một yêu cầu được đưa ra thông qua con trỏ lớp cơ sở để tham chiếu đến hàm ảo, C++ lựa chọn hàm thích hợp trong lớp dẫn xuất thích hợp gắn với đối tượng đang được trỏ tới.
Thông qua việc sử dụng hàm ảo và tính đa hình, một lời gọi hàm thành phần có thể có những hành động khác nhau dựa vào kiểu của đối tượng nhận được lời gọi.
Các lỗi thường gặp
Cho con trỏ lớp dẫn xuất chỉ đến đối tượng lớp cơ sở và gọi tới các hàm thành phần không có trong lớp cơ sở.
Định nghĩa lại trong lớp dẫn xuất một hàm ảo trong lớp cơ sở mà không đảm bảo chắc chắn rằng phiên bản mới của hàm trong lớp dẫn xuất cũng trả về cùng giá trị như phiên bản cũ của hàm.
Khai báo đối tượng của lớp trừu tượng.
Khai báo hàm thiết lập là hàm ảo.
Một số thói quen lập trình tốt
Khi thừa kế các khả năng không cần thiết trong lớp dẫn xuất, tốt nhất nên định nghĩa lại chúng.
Bài tập
Bài 5.1. 
Mô phỏng các thao tác trên một cây nhị phân tìm kiếm với nội dung tại mỗi nút là một số nguyên.
Bài 5.2. 
Mô phỏng hoạt động của ngăn xếp, hàng đợi cây nhị phân dưới dạng danh sách móc nối sử dụng mô hình lớp.

File đính kèm:

  • docLập trình hướng đối tượng với C++ - Chương 5 Kỹ thuật thừa kế.doc