Sự kế thừa (Inheritance)

Một phần của tài liệu Lập trình hướng đối tượng (Trang 31)

4.1. Khái niệm sự kế thừa

- Sự kế thừa là quá trình mà các đối tợng của lớp này đợc quyền sử dụng một số tính chất của các đối tợng của các lớp khác. Lớp trên gọi là lớp cơ sở (base class), lớp mới nhận đợc gọi là lớp dẫn xuất (derivative class)

- Nguyên lý kế thừa tạo ra cấu trúc phân cấp các lớp (đồ thị dạng cây).

- Trong lập trình hớng đối tợng, khái niệm kế thừa kéo theo ý tởng sử dụng lại, nghĩa là từ một lớp đã xây dựng, ta có thể bổ sung một số tính chất riêng để tạo ra một lớp mới mà không làm thay đổi những cái đã có.

4.2. Phân loại kế thừa:

Có 5 loại kế thừa: Kế thừa đơn, Kế thừa đa mức, Kế thừa phân cấp, Kế thừa bội, Kế thừa kép (lai ghép)

1) Kế thừa đơn

Điểm (x,y,c) ; // toạ độ và mầu sắc ĐoạnThẳng(x2,y2); // đầu mút thứ 2

Tamgiác(x3,y3); // đỉnh thứ 3 của tam giác

2) Kế thừa đa mức 3) Kế thừa phân cấp 4) Kế thừa bội 5) Kế thừa lai ghép 32 A B Lớp ông - Lớp cơ sở

Lớp cha - Lớp dẫn xuất trung gian C Lớp con - Lớp dẫn xuất A B1 B2 B3 C1 C2 D E1 E2 A B C A B C

5. Sự đa hình và sự tải bội (Polymorphism and Oveloading)

5.1. Khái niệm về sự đa hình (sự tơng ứng bội)

Sự đa hình (Polymorphism) đợc hiểu một cách trừu tợng là khả năng một khái niệm có thể xuất hiện ở nhiều dạng khác nhau.

Hàm và toán tử tải bội là một ví dụ của tính đa hình, tuy nhiên, sự tải bội mà chúng ta nhìn thấy ở đây là sự liên kết tĩnh. Hàm đợc chọn ở đây là trong thời gian dịch chơng trình và điều này làm giới hạn tính đa hình. Trong ngôn ngữ C++, các hàm đợc chọn trong khi chạy chơng trình và đây là một kỹ thuật trong sự kế thừa ở C++. Sự đa hình hiểu theo cách trên có lẽ nên dịch là sự tơng ứng bội thì thích hợp hơn vì nó nhằm trả lời câu hỏi: Ngoài cơ chế cho phép tải bội của các hàm và toán tử trong một lớp, thì một con trỏ đối tợng sẽ liên kết với hàm nào trong các lớp có quan hệ kế thừa mà các hàm này có chung đặc tính (cùng

tên và cùng tham số, kiểu tham số và kiểu của hàm) ? Rõ

ràng ta đang xét đến sự tơng ứng không phải là một mà là tơng ứng bội trong các liên kết tĩnh (trong thời gian dịch chơng trình) và liên kết động (trong thời gian chạy chơng trình). Và, từ đây ta sẽ lý giải rõ các khái niệm vừa trình bầy.

Tơng ứng bội đóng vai trò quan trọng trong việc tạo ra các đối tợng có cấu trúc bên trong khác nhau nhng có khả năng cùng dùng chung một giao diện bên ngoài (nh tên gọi). Điều này có nghĩa là một lớp tổng quát các phép toán

đợc định nghĩa theo các thuật toán khác nhau nhng có khả năng sử dụng theo cùng một cách giống nhau. Tơng ứng bội là mở rộng khái niệm sử dụng lại trong nguyên lý kế thừa.

Ví dụ:

Hàm VE() là hàm tơng ứng bội và nó đợc xác định tùy theo ngữ cảnh khi sử dụng.Trong lập trình hớng đối t- ợng với C++, tơng ứng bội có 2 loại:

Loại 1: Tơng ứng bội trong thời gian dịch chơng trình: Đó là sự cho phép định nghĩa và thực hiện các toán tử tải bội (operator overloading) và các hàm tải bội (function overloading). Ví dụ một toán tử gốc của ngôn ngữ là phép +, chỉ có thể thực hiện đợc đối với kiểu dữ liệu cơ sở nh int, float, double, char chứ không thể thực hiện đối với các kiểu dữ liệu do ngời dùng định nghĩa, ví dụ nh

Tương ứng bội

Tương ứng bội trong

thời gian dịch chương Tương ứng bội trong thời gian chạy chương trình

Hàm tải bội Toán tử tải bội Hàm ảo

HINH_HOC VE()

HINH_TRON()

VE(TRON) VE(DA_GIAC)DA_GIAC DUONG_THANG VE(D_THANG)

các số phức, các vector, các ma trận cùng kích thớc... Nh vậy, lập trình hớng đối tợng cho phép giữ nguyên ký hiệu phép cộng + để định nghĩa bổ sung phép cộng cho các kiểu dữ liệu mới. Khi đó ta có định nghĩa chồng toán tử, hay toán tử đó có khả năng tải bội. Tơng tự có thể định nghĩa các hàm cùng tên trong một lớp nhng thực hiện các chức năng khác nhau. Ví dụ nh các constructor của lớp là một tr- ờng hợp của hàm tải bội; chuyển đổi từ kiểu lớp sang kiểu cơ sở đợc coi là hàm tải bội. Nếu hiểu theo nghĩa "phép toán là tác động lên dữ liệu, làm thay đổi dữ liệu" thì toán tử tải bội và hàm tải bội đều là các phép toán tải bội.

Loại 2: tơng ứng bội trong thời gian chạy chơng trình. Đó là sự cho phép định nghĩa và thực hiện các hàm cùng tên, chung đặc tính trong lớp khác nhau có quan hệ kế thừa nhau. Nếu nh tơng ứng bội trong thời gian dịch ch- ơng trình quan tâm đến các hàm và toán tử cùng tên trong một lớp thì tơng ứng bội trong thời gian chạy chơng trình quan tâm đến những hàm và phép toán cùng tên và chung đặc tính giữa các lớp có quan hệ kế thừa và cách ứng xử của các đối tợng tơng ứng nh thế nào khi gọi thực hiện các hàm này.

5.2. Liên kết tĩnh và liên kết động

Tơng ứng bội trong thời gian dịch chơng trình và t- ơng ứng bội trong thời gian chạy chơng trình liên quan đến khái niệm liên kết tĩnh và liên kết động (có tài liệu còn gọi liên kết tĩnh là liên kết sớm hay ràng buộc sớm - Early Binding; và gọi liên kết động là liên kết muộn hay ràng buộc muộn - Late Binding).

Trong chơng trình hớng đối tợng với C++, cần phải trả lời câu hỏi: Một con trỏ đối tợng sẽ liên kết với hàm nào trong các lớp có quan hệ kế thừa mà các hàm này có chung đặc tính?

- Nếu mối liên kết giữa đối tợng với hàm đó đã có thể xác định đợc ngay từ khi dịch chơng trình và sẽ không thay đổi trong suốt thời gian chạy chơng trình thì đợc gọi là liên kết tĩnh. Nh vậy các phép toán tải bội trong một lớp và các hàm cùng đặc tính giữa các lớp nếu không có một chỉ định đặc biệt nào đó (ở đây là cài đặt cơ chế hàm ảo) thì đều là các phép toán đợc thực hiện theo sự liên kết tĩnh.

- Ngợc lại, nếu mối liên kết này chỉ có thể xác định đợc tại những thời điểm khác nhau lúc chạy chơng trình thì gọi là liên kết động. Liên kết động đợc giải quyết bằng cơ chế hàm ảo (virtual function). Cơ chế hàm ảo cho phép định nghĩa các hàm trừu tợng ở lớp trên và cài đặt cụ thể ở các lớp dới, sau đó, lúc chạy chơng trình, con trỏ đối tợng sẽ tùy theo ngữ cảnh mà gọi hàm nào trong các lớp có quan hệ kế thừa.

Những nhận xét mở đầu này sẽ đợc trình bầy cụ thể khi nghiên cứu về sự kế thừa.

chơng 2:Lớp các đối tợng

Bài 1. lớp các đối tợng 1. Khai báo lớp các đối tợng 1.1. Khai báo class class_name { private: return_type var_name; return_type fun_name(arg_list); public: return_type var_name; return_type fun_name(arg_list); }; 1.2. Tạo lập đối tợng

a)Tạo lập đối tợng ngay sau khi khai báo

class class_name

{ // Các thành phần của lớp } obj_name;

b)Tạo lập đối tợng bên ngoài lớp

class class_name obj_name;

1.3. Phạm vi hoạt động của các thành phần bên trong lớp

- Những thành phần đợc khai trong lớp đợc chia thành 2 nhóm:

+ Những thành phần private chỉ có thể đợc truy nhập bởi các hàm thành phần khác bên trong chính lớp đó, tức là chúng chỉ đợc sử dụng bên trong thân các hàm thành phần của lớp. Những thành phần private do đó không thể truy nhập bởi những hàm bên ngoài lớp thậm chí không thể truy nhập thông qua chính đối tợng thuộc lớp đó (thông qua toán tử chấm: ".", là toán tử xác định thành phần của đối t- ợng).

+ Những thành phần public có thể đợc truy nhập bởi các hàm thành phần khác bên trong lớp và các hàm bên ngoài lớp. Nếu truy nhập bởi các hàm bên ngoài lớp thì phải sử dụng thông qua đối tợng của lớp. Các đối tợng thuộc lớp sẽ truy nhập tới các thành phần public thông qua toán tử xác định "."

- Các hàm bên ngoài lớp tức là các hàm thành phần của lớp khác và các hàm bình thờng trong chơng trình, không thuộc lớp nào cả (gọi là các hàm ngoại lai).

Vùng private Vùng public Data function Data function

1.4. Định nghĩa các hàm thành phần

a)Định ngay bên trong lớp

class class_name { private:

// khai báo dịnh nghĩa các thành phần private public:

// khai báo các biến public return_type fun_name(arg_list) { // Các lệnh trong thân hàm } // các hàm thành phần khác }; Ví dụ 1 (exam1.cpp) #include <iostream.h> #include <conio.h> #include <stdio.h> class MEMBER { char name[25]; int age; public: void get_data(void)

{ cout<<"Enter name: "; gets(name); cout<<"Enter age: "; cin>>age; } void put_data(void) { cout<<"Name: "<<name<<"\n"; cout<<"Age: "<<age<<endl; } } MEMBER; int main() { clrscr(); MEMBER.get_data(); MEMBER.put_data(); getch(); return 0;}

b)Định nghĩa bên ngoài lớp return_type class_name::fun_name(arg_list) {// nội dung hàm } Ví dụ 2 (exam2.cpp) #include <iostream.h> #include <conio.h> #include <stdio.h> class SINHVIEN { char hoten[25]; int namsinh; public: void nhap(void); void xem(void); }; void SINHVIEN::nhap()

{ cout<<"Nhap ho ten sinh vien: "; gets(hoten); cout<<"Nhap nam sinh: "; cin>>namsinh; } void SINHVIEN::xem()

{ cout<<"Ho ten: "<<hoten<<endl;

cout<<"Nam sinh: "<<namsinh<<endl;} int main() {

clrscr(); SINHVIEN s;

s.nhap(); s.xem(); getch(); return 0;}

Truy nhập đến thành phần public thông qua đối tợng :

obj_name.var_member; obj_name.fun_member;

Chú ý: các thành phần private (dữ liệu và hàm) không

truy nhập đợc bên ngoài lớp, thậm chí cũng không thể truy nhập thông qua chính đối tợng của lớp đó.

Ngoài thành phần private và public, trong lớp còn có thành phần protected đợc dùng trong quan hệ kế thừa giữa các lớp, ta sẽ nghiên cứu sau.

2. Các thành phần trong lớp (Data member and function member) function member) 2.1. Khái quát Dữ liệu thành phần trong 1 lớp có thể có các kiểu sau đây: Hàm thành phần trong 1 lớp có thể có các kiểu sau đây:

1. Kiểu private - dữ liệu riêng

2. Kiểu public - dữ liệu chung

3. Kiểu protected - dữ liệu protect

4. Kiểu class - dữ liệu kiểu lớp

5. Kiểu static - dữ liệu tĩnh

1. Kiểu private - hàm thành phần riêng 2. Kiểu public - hàm thành phần chung 3. Kiểu protected-hàm thành phần protect 4. Kiểu static- hàm thành phần tĩnh 5. Constructor 6. Destructor 7. friend - hàm thành phần thân thiện

8. Operator overloading - Toán tử tải bội

9. function overloading - Hàm tải bội

10. virtual function - Hàm ảo * Trong bài này, ta chỉ nghiên cứu những điểm mục in nghiêng.

2.2. Thành phần private và thành phần public

- Các thành phần (dữ liệu và hàm) private chỉ có thể truy nhập bên trong thân các hàm thành phần trong lớp mà

không thể truy nhập bên ngoài lớp thông qua đối tợng của lớp. Ví dụ 3 class A { private: long x; void f(); public; double y; void g(); }; int main() { A a; a.x=10; // sai a.f(); // sai a.y=20; // dung a.g() ; // dung } Ví dụ 4

Xây dựng lớp các sinh viên, trong đó mỗi sinh viên bao gồm các thông tin: số báo danh, tên và năm sinh.

Tạo một mảng các đối tợng là các sinh viên. - Nhập dữ liệu cho các sinh viên

- In lên màn hình danh sách các sinh viên có năm sinh nào đó nhập từ bàn phím.

- In lên màn hình danh sách các sinh viên đợc sắp xếp tăng dần theo tên.

#include <iostream.h> #include <conio.h> #include <stdio.h> #include <string.h> class SINHVIEN { int sbd; char ten[30]; int ns;

public:

char *getten();

int getns() {return ns;} void nhapsv();

void xemsv(); };

char* SINHVIEN::getten() {int len=strlen(ten);

char *s = new char[len]; strcpy(s,ten);

return s; }

void SINHVIEN::nhapsv()

{ cout<<"so bao danh : "; cin>>sbd; cout<<"ten: "; gets(ten);

cout<<"nam sinh: "; cin>>ns;} void SINHVIEN::xemsv() { cout<<sbd<<" " <<ten<<" " <<ns<<endl; } const n = 4; SINHVIEN a[n]; void nhapdssv()

{ for(int i=0;i<n;i++) {a[i].nhapsv();} } void xemdssv()

{ for(int i=0;i<n;i++) {a[i].xemsv();} cout<<endl; }

void xemns() { int x;

cout<<"nhap nam sinh : "; cin>>x;

cout<<"danh sach sinh vien sinh nam "<<x<<":\n"; for(int i=0;i<n;i++)

if (a[i].getns()==x) a[i].xemsv(); cout<<endl; }

void swap(int i,int j)

{SINHVIEN tg=a[i]; a[i]=a[j]; a[j]=tg; } void sapxepten() { int i,j,k; for(i=0;i<n;i++) { k=i; for(j=i+1;j<n;j++) if (strcmp(a[j].getten(),a[k].getten())<0) k=j; if (k!=i) swap(i,k); } } void main() { nhapdssv(); xemns(); sapxepten(); xemdssv(); getch(); } 2.3. Dữ liệu thành phần static

- Nếu đặt từ khoá static trớc các khai báo thành phần thì dữ liệu đó là dữ liệu thành phần tĩnh (dữ liệu thành phần static)

- Dữ liệu thành phần static chỉ có một bản sao duy nhất dùng chung cho tất cả các đối tợng trong một lớp.

Ví dụ khai báo class A { static int c; int n; ... } a,b;

sẽ tạo ra 2 đối tợng a và b có chung thành phần dữ liệu c;

b.n b.c a.c

- Khi đối tợng đầu tiên của lớp đợc tạo lập thì những biến static đợc tự khởi gán bằng 0.

- Vì thành phần static dùng chung cho tất cả các đối tợng của lớp nên không thể truy nhập thành phần static thông qua một đối tợng cụ thể nào trong lớp mà phải thông qua chính tên lớp cùng với toán tử phân giải miền xác định.

- Trớc khi sử dụng các đối tợng trong lớp phải có 1 lệnh đặc biệt khởi tạo biến static. Nếu không có lệnh này thì chơng trình sẽ báo lỗi.

static_type class_name::var_static; Ví dụ 5 (static1.cpp) #include <iostream.h> #include <conio.h> class X { static int c; int n; public:

void get(int a) { n=a; c++; } void put(void) {cout<<" c :"<<c<<" n = "<<n<<endl; } }; int X::c; int main() { clrscr(); X a,b,c;

a.get(10); b.get(20); c.get(30); a.put(); b.put(); c.put();

getche();return 0; } * Nhận xét:

- Vì c là biến static nên nó chỉ có 1 bản sao duy nhất dùng chung cho 3 đối tợng a,b,c trong chơng trình. Mỗi khi một đối tợng mới đợc tạo, giá trị của biến static sẽ đợc xét

Kết quả: c: 3 n = 10 c: 3 n = 20 c: 3 n = 30

tới. Trong trờng hợp này, giá trị của biến static c lần lợt tăng đến 3 và dùng chung cho cả a,b,c. Ngợc lại với c, biến n không phải là biến static nên n có 3 bản sao tơng ứng với 3 đối tợng khác nhau.

2.4. Hàm thành phần static

- Nếu đặt từ khoá static trớc các khai báo hàm thành phần thì đó là hàm thành phần tĩnh (hay hàm thành phần static)

- Hàm thành phần static chỉ có thể truy nhập tới

những thành phần tĩnh ở trong lớp (tất nhiên, ngợc lại

các thành phần tĩnh (dữ liệu và hàm) có thể truy nhập bởi các hàm thành phần bình thờng khác). Nh vậy hàm thành phần tĩnh đợc sử dụng để thực hiện những công việc có tính chất chung chung cho tất cả các đối tợng trong lớp, chẳng hạn để truy nhập tới dữ liệu thành phần tĩnh.

- Cũng giống nh dữ liệu thành phần tĩnh, hàm thành phần tĩnh không là của riêng từng đối tợng trong lớp, do đó không truy nhập thông qua tên đối tợng mà đợc gọi thông qua tên lớp cùng với toán tử phân giải miền xác định.

class_name:: static_function(arg_list) ;

Ví dụ 6

#include <iostream.h> #include <conio.h> class MEMBER { static int count; double account; public:

void get_member(double value) {count++; account=value;}

void show_account(){cout<<"account: "<<account<<endl;} static void show_count()

}; int MEMBER::count; int main() { clrscr(); MEMBER t1,t2,t3; t1.get_member(100); t2.get_member(200); t3.get_member(300);

MEMBER::show_count(); // kq: number of members:3 t1.show_account(); // kq: member: 100

t2.show_account(); // kq: member: 200 t3.show_account(); // kq: member: 300 getche();

return 0; }

2.5. Dữ liệu thành phần kiểu lớp class

- Cũng nh kiểu dữ liệu struct, class cũng có thể coi là một kiểu dữ liệu và do đó, một thành phần của lớp cũng có thể có kiểu class. Ví dụ 7 #include <iostream.h> #include <conio.h> class A { public: int x; class SubA { public: int y; }; }; int main() { A a; A::SubA a1; int m=a.x=10; int n=a1.y=20;

cout<<"class A, x = "<<m<<endl; cout<<"Sub class A, y = "<<n<<endl; getche(); return 0; } Ví dụ 8 class First { public: int n;

// cac ham thanh phan

Một phần của tài liệu Lập trình hướng đối tượng (Trang 31)

Tải bản đầy đủ (DOC)

(174 trang)
w