1. Trang chủ
  2. » Giáo án - Bài giảng

Giáo trình kỹ thuật lập trình hướng đối tượng bằng c++ phần 2

128 4 0
Tài liệu được quét OCR, nội dung có thể không chính xác

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Nội dung

Trang 1

Chương 5

KỸ THUẬT KẾ THỪA

5.1 LỢI ÍCH CỦA VIỆC KẾ THỪA

Sự kế thừa là một phần không thể thiếu của lập trình theo hướng đối tượng Nó là một lợi thế, cho phép ta có thể sử dụng lại mã lệnh Một lớp cơ sở được viết và biên dịch, nó không cần thiết phải biên dịch lại Tuy nhiên, việc sử dụng kế thừa có thể được đưa vào các tình huống khác nhau Sử dụng lại mã lệnh sẽ tiết kiệm thời gian và làm tăng tính tin cậy của chương trình Sự kế thừa cũng có thể giúp ích trong mô phỏng các vấn đề và thiết kế toàn

diện của chương trình

Một kết quả quan trọng của tính sử dụng lại là dé dang phân phối các

thư viện lớp Người lập trình có thể sử dụng một lớp đã được tạo ra bởi một

ai đó hay một công ty mà không cần phải sửa đổi nó

5.2 TÍNH KẾ THỪA

5.2.1 Lớp kế thừa

Một lớp được xây dựng và kế thừa từ một lớp khác gọi là !ớp kế thừa

Trang 2

Hơn thế nữa, một lớp có thể là cơ sở cho nhiều lớp kế thừa khác nhau Đến lượt mình, lớp kế thừa lại có thể dùng làm lớp cơ sở để xây dựng các lớp kế thừa

khác; ngoài ra nó có thể kế thừa từ nhiều lớp cơ sở (hình 5.2 và 5.3) Khi xây dựng lớp, người ta thường dùng mũi tên đi lên nhằm chỉ ra lớp ở dưới kế Động vật Động vật có vú Loài bò sát thừa lớp ở trên Ran Than lan Ngua Chó Hình 5.2 Kiến trúc phân tầng kế thừa của một số loài động vật Công dân >> Người quản lý Cán bộ giảng dạy Trưởng bộ môn Hình 5.3 Đa kế thừa

Trang 3

5.2.2 Cách xây dựng lớp kế thừa Giả sử ta đã định nghĩa lớp A, để xây dựng lớp C là kế thừa từ lớp A ta viết: class C: public A { 1z

5.2.3 Kế thừa private, public và thuộc tính protected

Nếu muốn định nghĩa lớp C kế thừa từ hai lớp A và B đã định nghĩa, ta viết: class C: public A, public B

{ he

Ta thấy rằng, ở khai báo trên, lớp C kế thừa theo kiểu public Nếu

thay public bang private thì nó sẽ kế thừa theo kiểu private

Với khai báo:

class C: public A, B

{

Mi

thì lớp A là lớp cơ sở pub1ic của lớp C, lớp B là lớp cơ sở private của lớp C Ta có các tinh chat sau:

— Nếu kế thừa theo kiểu public thi tất cả thành phần pub1lic của lớp

cơ sở sẽ trở thành thành phần public của lớp kế thừa -

— Nếu kế thừa theo kiểu pzivate thì tất cả thành phần pub1ic của lớp cơ sở sẽ trở thành thành phần private của lớp kế thừa

— Tuy lớp kế thừa kế thừa các thành phần dữ liệu và phương thức của

lớp cơ sở, nhưng ở trong các phương thức của lớp kế thừa không được phép

Trang 4

truy cập trực tiếp đến các thành phân dữ liệu private của lớp cơ sở Để khắc phục điều này, ta thay từ khoá private của lớp cơ sở thành từ khoá

protected |

— Lép ké thita kế thừa được các phương thức của lớp cơ SỞ, trừ Các

phương thức sau đây thì không được kế thừa: hàm tạo, hàm huỷ, toán tử gan

5.3 DON KE THUA |

Đơn kế thừa là một lớp kế thừa duy nhất từ một lớp khác (hình 5.1) Trong thế giới thực xung quanh chúng ta có rất nhiều đối tượng thuộc một lớp nào đó là kế thừa từ lớp khác Ví dụ sau xây lớp Congdan, trong lớp này có: họ tên, quê quán, số chứng minh thư và các phương thức thành viên,

tiếp đến ta xây dựng lớp Tai khoan kế thừa lớp Congdan Vi du 5.1 Lớp tài khoản kế thừa lớp công dân #include<iostream.h> #include<string.h> #include<iomanip.h> #include<conio.h> class Congdan { private:

char ht{ 20}, qal 30); //ht:ho tén, qq: quê quán

Trang 5

/ stk: s6 tài khoản, sđ: số dư tài khoản, pin: mã pin long int stk,., sd; long int pin; public: Taikhoan() { stk= 0; sd= 0; pin= 0; } void rhap ()-; void hienthi(); Jie 150 © void Congdan: :nhap () {

cout<<"\nHo va ten: "; cin.getline(ht, 20);

cout<<"Que quan: "; cin.getline (qq, 30); cout<<"So CMT: "; cin>>cmt; } void Congdan: :hienthi () { Cout<<ht<<setw (10) <<qq<<setw (10)<<emt¿ } void Taikhoan: :nhap() { " Congdan: :nhap();

cout<<"So tai khoan: "; cin>>stk; cout<<"Ma pin: "; cin>>pin;

Trang 6

Taikhoan a,b; cout<<"\Nnhap TAI KHOAN A\n"; a.nhap(); cín.ignore (1); cout<<"\Nnhap TẠI KHOAN B\n"; b.nhap();

cout<<"\nTai khoan a\n"; a.hienthi();

¡ cout<<"\nTai khoan b\n”; b.hienthi();

getch();

}

Kế quả:

NHAP TAI KROAN A

Ho va ten: Nguyen Tuan Anh

Que quan: Bac Ninh

So CMT: 1250

So tai khoan: 1234

Ma pnhap: 234569

So du tai khoan: 1000000

NHAP TAI KHOAN B

Ho va ten: Nguyen Quang Khanh Que quan: Thai Binh So CMT: 13456 So tai khoan: 12345 Ma pnhap: 123456 So du tai khoan: 2000000 Tai khoan À: Nguyen Tuan Anh Bac Ninh 1250 1234 234569 -1000000 Tai khoan B Nguyen Quang Khanh Thai Binh 13456 12345 123456 2000000

5.4 HẦM TẠO LỚP KẾ THỪA VÀ HÀM TẠO LỚP CƠ SỞ

Trong phần này sử dụng hai lớp là Congdan và Tai khoan, ta kiểm tra

sự hoạt động của hàm tạo lớp cơ sở và hàm tạo lớp kế thừa Chương trình dưới đây khai báo một đối tượng thuộc lớp kế thừa rồi nhập và hiển thị dữ

liệu của nó lên màn hình

Trang 7

Ví dụ 5.2 Hàm tạo lớp kế thừa và hàm tạo lớp cở sở #include <iostream.h> #include <string.h> #include <iomanip.h> #include <conio.h> class Congdan ( private: char ht[ 20], gợi 30] ; long int cmt; public: Congdan () { cout<<"\nHam tao lop co so"; strepy(ht," "); strcpy(qq," "); cmt= 0; } void nhap(); void hienthi(); he void Congdan: :nhap () {,

cout<<"\nNhap cong dan\n";

cout<<"\nHo va ten: "; cin.getline(ht,20);

Trang 8

cout<<"\nHam tao lop co ke thua”; stk= 0; sd= 0; pnhap= 0; } ‘void nhap(); void hienthi(); LG ‘void Taikhoan: :nhap () { Congdan: :nhap ();

' eout<<"So tai khoan: "; cỉn>>stk; cout<<"Ma PIN: "; cin>>pnhap;

cout<<"So du tai khoan: "; cin>>sd; } void Taikhoan: :hienthi () { Congdan::hienthi (); cout<<setw (10)<<stk<<setw (10)<<pnhap<<setw (10)<<sd; } void main() { clirscr(); Taikhoan a; a.nhap()z + a,hienthi(); getch(); } Két qua:

Ham tao lop co so Ham tao lop co ke thua

Nhap cong dan

Trang 9

Từ kết quả nhận được ta thấy rằng, khi đối tượng của lớp Taikhoan

được tạo thì đầu tiên hàm tạo lớp cơ sở Congdan được gọi trước, sau đó hàm

tạo lớp Tai khoan mới được gọi

Trong nhiều tình huống muốn nạp chồng hàm tạo lớp kế thừa để khai báo và khởi tạo đối tượng với các giá trị ban đầu, chẳng hạn:

Tai khoan (char ht[], char qdg[],long int.scmt, long _ dint sotk, long int mapin,long int sodu): Congdan (ht, qq, scmt) cout<<"\nHam tao lop ke thua duoc nap chong\n"; stk= sotk; ` sd= sodu; pin= mapin; }

Do lớp Taikhoan ké thita lé6p Congdan, do đó phải truyền giá trị họ tên, quê quán và số chứng minh thư cho lớp cơ sở, nên phải nạp chồng hàm tạo lớp cơ sở Congdan ‘

Trang 10

1 1 Congdan(char hoten{], char quequan[], long int scmt) { cout<<"\nHam tao lop co so duoc, nap chong\n"; strepy (ht, hoten); strepy (qq, quequan) ; cmt=scmt; } void nhap(); :void hienthi(); }; void Congdan: :nhap() {

cout<<"\nNHAP CONG DAN\n";

cout<<"\nHo va ten: "; cin.getline(ht,20); cout<<"Que quan: "; cin.getline (qq, 30); cout<<"So CMT: "; cin>>¢mt; } void Congdan: :hienthi () { cout<<ht<<setw (10) <<qq<<setw(10)<<emt; do : class Taikhoan: public Congdan { private: long int stk, sd; long int pin; public: Tai khoan () { ' cout<<"\nHam tao lop co ke thua\n"; stk= 0; sd= 0 pin= 0; ` }

Taikhoan(char ht{], char qq[},long int scmt, long int sotk, long int mapin,long int sodu): Congdan (ht, qq, scmt)

Trang 11

{ cout<<"\nHam tao lop ke thua duoc nap chong\n"; stk= sotk; sd= sodu; pin= mapin; void nhap(); void hienthi(); Mi void Taikhoan: :nhap() { Congdan: :nhap (};

cout<<" So tai khoan: "; cin>>stk; Cout<<"Ma PIN: "; cin>>pin;

Trang 12

Kết quả:

| TAI KHOAN a

Ham tao lop co so Ham tao lop co ke thua Nhap cong dan

Ho va ten: Nguyen Tuan Anh Que quan: Bac Ninh

So CMT: 12345 , So tai khoan: 884192 Ma PIN: 68841

So.du tai khoan: 100000

Nguyen Tuan Anh Bac Ninh 12345 884192 68841 100000

TAI KHOAN b

Ham tao lop co so duoc nap chong Ham tao lop ke thua duoc nap chong

Nguyen Gia Bao Ha noi 123456 654355356 6884192 20000000

5.5 THỨ TỰ GỌI HẦM TẠO CỦA LỚP CƠ SỞ VÀ LỚP KẾ THỪA

Trang 13

char ht[ 20], qaf 30]; long int emt; public: Congdan (} { strcpv(ht," "); strcpy(qq," "); cmt= 0; } Congdan (char htị 20], char ga 30), long int cmt) ' - : - : cout<<"\nTao doi tuong cong dan\n"; strcpy (this->ht,ht); Strcby (this->qq, qq) ; this->cmt=cmt; } void nhap(); void hienthi(); Me class Taikhoan: public Congdan { ˆ ` Ppriväte: long int stk, sd; long int pin; public: » Taikhoan() fo, stk= 0; sd= 0; pin= 0; } ,

Trang 14

this->pin=pin;

}

void nhap(); void hienthi();

int operator >(Taikhoan b); int operator <(Taikhoan b);

biG

void Congdan::nhap()

{

cin.ignore (1);

cout<<"\nHo va ten: ";cin.getline (ht,20); cout<<"Que quan: "; cin.getline (qq, 30); cout<<"So CMT: "; cin>>cmt; } void Congdan::hienthi () { cout<<ht<<setw (10) <<qq<<setw(10)<<cmt; } void Taikhoan: :nhap() { Congdan: :nhap();

cout<<"So tai khoan: "7 cin>>stk; cout<<"Ma pin: "; cin>>pin;

Trang 15

int kq= 1; if (this->sd >= b.sd) kq= 0; else kq= l1; return kq; } void main () { clrscr(); Taikhoan a ("Nguyen Tuan Anh" ,"Thuan Thanh, Bac Ninh",125066999,12345,2000000,123456); cCout<<"\nTai khoan a\n"; a.hienthi(); getch(); } Két qua:

Tao doi tuong cong dan

Tao doi tuong tai khoan Tai khoan a Nguyen Tuan Anh Thuan Thanh, Bac Ninh 125066999 12345 123456 2000000

5.6 HAM TAO SAO CHEP CUA LOP CO SO

Trong chương 3 ta đã biết lý đo tại sao phải xây dựng hàm tạo sao chép, chính vì sự có mặt của các thành phần đữ liệu là con trỏ cho nên nó đã làm thay đổi dữ Hiệu của biến đối tượng (xe1) khi khai báo và khởi tạo giá trị ban _đầu cho một đối tượng khác (xe2) bởi giá trị của đối tượng này

Như vậy, cần xây dựng hàm tạo sao chép, bởi vì trong lớp cơ sở cũng có thể có thành phần dữ liệu là con trỏ Cũng có khi ta khai báo và khởi tạo giá trị cho một đối tượng của lớp cơ sở bởi giá trị của đối tượng cùng lớp Lập luận tương tự đối với lớp kế thừa nếu có thành phần dữ liệu là con trỏ thì phải xây dựng hàm tạo sao chép cho lớp

Ví dụ dưới đây khai báo thành phần số chứng minh thư là con trỏ trỏ

Trang 17

162 public: Tai khoan () { stk= 0; sd= 0; pin= 0; }

Taikhoan(char ht{ 20], char qq 30], long int cmt, long int stk, long int sd, long int pin): Congdan (ht, qq, cmt) , cout<<"\nTao doi tuong tai khoan\n"; this->stk=stk; this->sd =sd; this->pin=pin; } void nhap(); void hienthi(); MG void Congdan: :nhap () { long int x; 'eout<<"\nHo va ten: "; cin.getline(ht,20); cin.ignore (1);

cout<<"Que quan: "; cin.getline (qq, 30);

cout<<"So CMT: "; cin>>x; * (this->cmt)=x;; } void Congdan: :hienthi () { cout<<ht<<setw(10)<<" "<<qq<<setw(10)<<* cmt; } void Taikhoan: :nhap () { Congdan: :nhap ();

cout<<"So tai khoan: "; cin>>stk;

‘cout<<"Ma pin: "; cin>>pin;

cout<<"So du tai khoan: "; cin>>sd;

}

Trang 18

Congdan: :hienthi(); cout<<setw (10)<<stk<<setw (10) <<pin<<setw (10)<<sd; } void main() { clrscr();long int cmt; Taikhoan a("Nguyen Tuan Anh","Thuan Thanh, Bac Ninh", 125066999,12345, 2000000, 123456); cout<<"\nTai khoan a\n"; a.hienthi(); Taikhoan b(a);

cout<<"\nTai khoan b\n"; b.hienthi();

cout<<"\nThay doi so chung minh thu cua b la:";

cin>>cmt; '

b.thaydoicmt (cmt);

cout<<"\n Sau khi thay doi so chung minh thu cua b\n"; ,cout<<"\nTai khoan a\n"; a.hienthi();

cout<<"\nTai khoan b\n"; b.hienthi(); getch(); } Kết quả:

‘Tao doi tuong cong dan Tao doi tuong tai khoan Tai khoan a Nguyen Tuan Anh Thuan Thanh, Bac Ninh 125066999 12345 123456 2000000 Tai khoan b : ‘Nguyen Tuan Anh Thuan Thanh, Bac Ninh 125066999 12345 123456 2000000

Trang 19

Qua kết quả trên ta thấy rằng, khi b thay đổi số chứng minh thư thì số

chứng minh thư của a cũng thay đổi theo và chương trình còn thông báo một cảnh báo là: "NULL pointer assignment", nó cho biết sử dụng thành

phần con trỏ là rỗng, nguyên nhân của sự thay đổi số chứng minh thư của

đối tượng a là do biến cmt của lớp Congdan là con trỏ Bpi vì, khi khai báo

đối tượng b và khởi tạo bởi dữ liệu của đối tượng a như câu lệnh:

Taikhoan b(a);

thì hàm tạo sao chép mặc định sẽ chép nội dung các thành phần của đối tượng a cho đối tượng b tương ứng, con trỏ cmt của đối tượng a và b cùng trỏ

đến một vùng nhớ Do đó, khi b thay đổi số chứng minh thư bằng câu lệnh: b.thaydoicmt (cmt) ;

thì nội dung ô nhớ và cả hai con trd emt cla a và b đều trô đến bị thay đối, điêu này dẫn đến b thay đổi số chứng minh thư thì số chứng minh thư của a cũng thay đổi theo

Để giải quyết được vấn để này, ta xây dựng hàm tạo sao chép cho lớp cơ sở như dưới đây

Trang 20

Congdan(char ht[ 20], char qq 30], long int cm) { strcpy (this->ht,ht); strcpy (this->qg; qQ) / this->cmt=new long int; * (this->cmt) =cm; } Congdan(Congdan &p) { ’ strepy(this->ht,p-ht); strepy (this->qq,p.qq)i emt=new long int; * (this->emt)=* (p.cmt); } void nhap(); void hienthi(); void thaydoicmt (long int x) { * (this) ->cmt=x; } he class Taikhoan: ‘public Congdan private: long int stk, sd; long int pin; public: Tai khoan () { stk= 0; sd= 0; pin= 0; }

Taikhoan(char htị 20], char qaf 30}, long int cmt, long int stk, long int sd, long int pin): Congdan (ht, qq, cmt)

this->stk=stk;

Trang 21

this->sd =sd; this->pin=pin; } void nhap(); void hienthi(); ad void Congdan: :nhap () { long int x; cin.ignore(1);

cout<<"\nHo va ten: "; cin.getline (ht, 20);

cout<<"Que quan: "; cin.getline (qq, 30); cout<<"So CMT: "; cin>>x; * (this->cmt)=x;; } void Congdan: :hienthi() { cout<<ht<<setw (10) <<qq<<setw(10)<<* emt; } void Taikhoan: :nhap() { Congdan: : nhap () ¿

€out<<"§o tai khoan: ";¿ cin>>stk; cout<<"Ma pin: "; cin>>pin;

Trang 22

cout<<"\nThay doi so chung minh thu cua b la:"; cin>>cmt;

b.thaydoicmt (cmt);

' cout<<"\n Sau khi thay doi so chung minh thu cua b\n”; cout<<"\nTai khoan a\n"; a:hienthi();

cout<<"\nTai khoan b\n"; b,hienthi(); _ getch(); } Kết quả: Tai khoan a Nguyen Tuan Anh Thuan Thanh, Bac Ninh 125066999 12345 123456 2000000 Tai khoan Ð / Nguyen Tuan Anh -Thuan Thanh, Bac Ninh 125066999 12345 123456 2000000

Thay doi so chung minh thu cua b 1a:125088888 Sau khi thay doi so chung minh thu cua b Tai khoan a Nguyen Tuan Anh Thuan Thanh, Bac Ninh 125066999 12345 123456 2000000 Tai khoan b Nguyen Tuan Anh Thuan Thanh, Bac Ninh 125088888 12345 123456 2000000 Với kết quả này ta đã giải quyết được điều gặp phải ở trên 5.7 DA KE THUA :

_ Trong thế giới thực có rất nhiều đối tượng có cùng thuộc tính chung

nhất định, những đối tượng này được gom lại thành một lớp, trong mỗi đối

tượng thuộc lớp này có khi lại được cấu tạo bởi một số thành phân mà những thành phần này lại là các đối tượng thuộc một lớp khác, lớp được cấu tạo như

vậy là lớp được kế thừa từ các lớp khác và gọi là lớp đa kế thừa

Trang 23

Bộ nhớ trong Bộ nhớ ngồi ¬ Bảng mạch chủ CPU , Bộ nhớ Màn hinh Hộp máy Ban phim Chuột May vi tinh Ð Hình 5.5 Tỉnh đa kế thừa

Hình 5.5 cho thấy lớp Máy vi tính được kế từ các lớp Màn hình, Hộp máy, Bàn phím và Chuột; trong đó lớp Hộp máy lại kế thừa các lớp như là

Bảng mạch chủ, CPU và Bộ nhớ, tron¿ đó lớp Bộ nhớ lại được kế thừa từ lớp

Bộ nhớ trong và Bộ nhớ ngoài

Tính kế thừa rất đa dạng như hình 5.5 làm cho chúng ta khó kiểm soát và xử lý những tình huống xảy ra trong chương trình, vì thế cần giải quyết một số vấn đề sau:

—~ Các hàm tạo và hàm huỷ được gọi như thế nào?

— Sự đa kế thừa cho phép một lớp có thể là lớp kế thừa từ nhiều lớp cơ sở

Trang 24

Khai báo lớp C như sau:

class C: public A, public B

{

de

Bên trong lớp C có thể khai báo và định nghĩa các thành phần dữ liệu và các phương thức của lớp C Ta thực hiện lập hàm tạo cho lớp C, để thực hiện

vừa khai báo, vừa khởi tạo giá trị cho đối tượng của lớp C thì phải truyền khai báo dữ liệu cho các thành phần của lớp A, B và C Do đó, phải thực hiện truyền giá trị cho thành phần a, b của từng lớp kế thừa cơ sở A, B và c cho

lớp c Như vậy, phải nạp chồng hàm tạo lớp C như sau:

AB (int a0, int b0, int c0): A (a0), B(b0) {

| this->c = cŨ; có }

Trong hàm tạo lớp C này đã thực hiện truyền giá trị vào cho lớp A và lớp

bB là a0 và b0 tương ứng Như vậy, hàm tạo một đối số của lớp A và của cả

lớp B cũng được triệu gỌI ,

© Thứ tự gọi các hàm tạo: Các hàm tạo của các lớp cơ sở theo thứ tự

khai báo của các lớp cơ sở trong lớp kế thừa lần lượt được gọi, sau cùng là

hàm tạo của lớp kế thừa mới được gọi Trong khai báo và định nghĩa ba lớp A, B và C ở trên, hàm tạo của lớp A được gọi trước, sau đó là hàm tạo của

lớp B, cuối cùng là hàm tạo của lớpC được gọi

e Thứ tự gọi các hàm huỷ: Các hàm huỷ được gọi theo thứ tự ngược lại

với cách gọi các hàm tạo Trong khai báo ba lớp A, B và C ở trên, hàm huỷ của lớp C sẽ được gọi đầu tiên, rồi đến hàm huỷ của lớp B được gọi, cuối cùng là hàm huỷ của lớp A được gọi

ø Các thuộc tỉnh kế thừa: Đa kế thừa cũng sử dụng các thuộc tính kế thừa như kiểu kế thừa đơn

s Cách gọi các hàm thành phân của các lớp cơ sở: Nếu lớp A có hàm hienthi (), lớp B có hàm hienthi(), thì ở lớp C ta sử dụng bàm hienthi () của lớp nào thì phải chỉ rõ phạm vi hàm đó, ví.dụ:

Trang 25

void C:: hienthi() {

A::hienthi(); /* sử dụng hàm thành phần của lớp cơ sở A*/

}

Vi du sau minh hoa sự đa kế thừa, ta xây dựng lớp Điểm ảnh — Diemanh là các điểm ảnh tại toạ độ hai chiều (x, y) với độ xám (màu sắc)

hoặc màu nhất định của ảnh số Như vậy, lớp Diemanh sẽ kế thừa hai lớp

Toado2D và lớp Doxam theo kiểu pub1ic (hình 5.7) Toado2D Doxam double x, y int mau; Diemanh

Hình 5.7 Lớp Diemanh kế thừa từ hai lớp

Trang 27

172 } Diemanh () { cout<<"\nHam tao mac đỉnh lop Diemanh\n"; }

Diemanh (long int x0, long int y0, unsigned int c): Toado2D (x0, yO) , Doxam(c)

cout<<"\nHam tao mac dinh lop Diemanh \n"; } ~ Diemanh () { cout<<"\nHam huy lop Diemanh\n"; } void hienthi () { Toade2D :: hienthi(); Doxam :: hienthi(); } void main() { clrscr(); DiemanhA (3,4,5); Cout<<" ===================\ nh A.hienthi (); Cout<<" ===================\ nh ¿

cout<<"\n= goi phuong thuc hienthi (})

Trang 28

Kết quả:

Ham tao duoc nap chong lop Toado2D

Ham tao nap chong lop Doxam Ham tao mac dinh lop Diemanh Toa do: 3, 4 Mau: 5 ==Ỷ==rc===z========== = goi ham hienthi cua Toado2D Toa do:3, 4 = goi ham hienthi cua Diemanh Mau :5

Ham huy lop Diemanh Ham huy lop Doxam Ham huy lop Toado2D

CÂU HỎI VÀ BÀI TẬP

1 Bạn hãy hình dung là mình sẽ xuất bản một ấn phẩm bao gồm cả sách

và đĩa CD Hãy tạo một lớp Ấn phẩm bao gồm các thành phần là Tên và

Giá của một tài liệu xuất bản Từ đó xây dựng hai lớp trên là Sách và

Đĩa CD mà cả hai đều kế thừa lớp Ấn phẩm, trong đó lớp Sách có thêm các thành phần là Số trang và lớp Đĩa CD có thêm Thời gian chạy tối đa Tất cả ba lớp trên đều có các phương thức như là nhập đữ liệu từ bàn

phím — nhap() và hiển thị dữ liệu lên màn hình - hienthi () Hãy viết chương trình hàm main () để tạo ra đối tượng Sách và Đĩa CD rồi

yêu cầu người dùng nhập và hiển thị đữ liệu của các đối tượng đó lên

màn hình

2 Xây dựng lớp Hệ số của phương trình bậc nhất gồm có hai thành phần là

a và b, rồi viết các phương thức nhập hiển thị hệ số Hãy xây dựng lớp

Phương trình bậc nhất kế thừa lớp Hệ số trên, trong đó có viết phương thức giải phương trình cho lớp này Viết chương trình minh hoạ giải một phương trình bậc nhất với dữ liệu được nhập vào từ bàn phím

Trang 29

174

Xây dựng lớp Người gồm có các thành phần Họ tên và Năm sinh Viết

các phương thức của lớp như nhập, hiển thị dữ liệu Xây dựng lớp

Thí sinh kế thừa lớp này, trong lớp Thí sinh có Số báo danh và Điểm các

mơn tốn, lý, hoá Hãy xây dựng thêm các phương thức nhập, xuất, và

khai báo mảng có kiểu là Thisinh để quản lý đanh sách các thi sinh

Viết chương trình với các công việc sau: — Nhập vào một đanh sách có n thí sinh

—_ Hiển thị danh sách thí sinh với tổng điểm ba môn tăng dần;

— Tìm một thí sinh khi biết Số báo danh

- Thống kê xem có bao nhiêu phần trăm thí sinh đạt yêu cầu (cả ba

môn có điểm lớn hơn hoặc bằng 3)

Xây dựng lớp Sản phẩm bao gồm: Mã sản phẩm, Tên sản phẩm, Năm sản xuất và các phương thức nhập, xuất Từ đó xây đựng tiếp lớp Tivi có

thêm các thuộc tính Chiều dài, Chiều rộng của màn hình Hãy xây dựng phương thức tính diện tích của màn hình của lớp Tivi Viết chương trình

nhập các vào đối tượng của lớp Tivi, sau đó sắp xếp tăng dần theo diện

Trang 30

Chương 6

TÍNH ĐA HỈNH `

— — ——————DDDDD xxšĩằăưỤN

Ở trên chúng ta đã tìm hiểu một số điều về con trỏ và những cải tiến của C++ như: hàm bạn, lớp bạn, phương thức tĩnh, nạp chồng toán tử, nạp chồng hàm tạo và tạo hàm tạo sao chép Đây không những chỉ là các đặc tính cần

thiết cho lập trình C++ mà nó còn được sử dụng rộng rãi Trong chương này

chúng ta xét một đặc tính hữu ích nữa là phương thức ảo

6.1 PHƯƠNG THỨC ẢO VÀ TÍNH ĐA HỈNH

Ảo có nghĩa là tổn tại nhưng không có thật Khi các phương thức ảo được sử dụng trong một chương trình, nơi xuất hiện lời gợi một phương thức của một lớp có thể là phương thức có thực từ lời gọi trong một lớp khác

Tại sao chúng ta cần phương thức ảo? Bởi vì trong chương trình có thể có những đối tượng khác nhau của các lớp khác nhau nhưng ta muốn lưu trữ

chúng vào cùng một mảng, thực hiện một số phép toán và hàm chung cho

các đối tượng này Ví dụ, có một đối tượng là một tam giác, một hình tròn và một hình vuông; trong chương trình ta khai báo các đối tượng hình khác

nhau đó và muốn hiển thị các đối tượng lên màn hình theo cách hiển thị

riêng của từng đối tượng Một cách tiếp cận là tạo ra một mảng mà lưu trữ

được tất cả các đối tượng con trỏ trong cùng một mảng Mang này có thể:

khai báo như sau:

Hinh *ptr[ 100] ;

Biến ptr là một mảng các con trỏ, các con trỏ này thuộc kiểu Hinh

(hay là đối tượng của lớp Hình — ninh), ta sẽ lưu các đối tượng của các lớp

khác nhau vào trong mảng ptz Vậy, làm sao để thực hiện hiển thị được các đối tượng một cách chính xác? Câu hỏi sẽ được trả lời sau khi tìm hiểu và

xem xét ví dụ dưới đây

Trang 31

Giả sử ta có ba lớp là Tam giác, Hình tròn và Hình vuông; ba lớp này đều kế thừa lớp Hinh Ta khai báo ba đối tượng abc, c, hv lần lượt là các đối tượng của các lớp Tam giác, Hình tròn và Hình vuông; các đối tượng này được quản lý bởi mảng ptr bằng các câu lệnh:

ptr[ 0] =&abc; //con trỏ ptr[ 0] trỏ đến đối tượng của lớp Tamgiac

ptr[ 1] =&c; //con trỏ ptr[ 1] trỏ đến đối tượng của lớp Hỉnhtron ptr[ 2] =hv; // con trỏ ptr[ 2] trỏ đến đối tượng của lớp Hỉnhvuong

Như vậy, để hiển thị được ba đối tượng của mảng ptr ta thực hiện câu lệnh sau: for(int i = 0; i < 3; itt) ptr[ i} ->hienthi(); Đây là một điều hết sức ngạc nhiên, bởi vì câu lệnh: ptr[ i] ->hienthi();

sẽ thực hiện các phương thức hienthi () khác nhau mà cùng một lời gọi

Nếu con trỏ trong mảng ptr{ i] trỏ đến đối tượng hình tròn thì hàm hienthi () thuộc lớp Hình tròn sẽ được gọi; nếu con trỏ trỏ đến một tam giác thì hàm hienthi () thuộc lớp Tam giác được gọi, và nếu con trổ trỏ

Trang 32

Tính đa hình là khả năng thiết kế và cài đặt các hệ thống có thể mở rộng

đễ dàng hơn (hình 6.1) Các chương trình có thể được viết để xử lý tổng

quát, như các đối tượng lớp cơ sở, các đối tượng của tất cả các lớp tồn tại

trong một phân cấp Khả năng cho phép một chương trình sau khi đã biên

dịch có thể có nhiều diễn biến xảy ra là một trong những thể hiện của tính đa hình, tính muôn màu, muôn vẻ của chương trình hướng đối tượng

Một thông điệp được gửi đi (gửi đến đối tượng) mà không cần biết đối tượng nhận thuộc lớp nào Để thực hiện được tính đa hình, các nhà thiết kế C++ cho chúng ta dùng cơ chế kết nối động (dynamic binding) thay cho cơ chế kết nối tĩnh (static binding) ngay khi chương trình biên dịch được dùng trong các ngôn ngữ cổ điển như C, Pascal

Cơ chế kết nối tĩnh là cách mà trình biên dịch sẽ biên dịch tất cả các câu lệnh trong hàm main () cũng như tất cả các hàm tự do và phương thức của

các lớp sang dạng mã máy trước khi chương trình được thực hiện

Cơ chế kết nối động là cách mà chương trình dịch không biên dịch các phương thức ảo khi biên dịch chương trình sang dạng mã máy, mà nó sẽ biên dịch phương thức ảo của từng lớp kế thừa mỗi khi đối tượng của lớp đó gọi phương thức ảo của mình trong khi chương trình đang thực hiện

Tính đa hình là một trong những đặc tính của lập trình hướng đối tượng sau các lớp và tính kế thừa để có thể tiếp cận và làm việc với tính đa hình mà

chúng ta thường xuyên gặp phải Ví dụ 6.1 minh hoạ cho tính đa hình Trước

_tiên ta tạo ra các lớp hình khác nhau như Tamgiac, Hinhtron va Hinhvuong

mà chúng được kế thừa từ lớp cơ sở Hinh Tiếp theo xây dựng các phương thức hienthi () chỉ dùng thực hiện hiển thị lên màn hình thông báo là các xâu ký tự cho biết đối tượng gọi phương thức này là đối tượng thuộc lớp nào, chẳng hạn như: "Đay la Hinh", "Day la Hinh tron”

Trang 34

Kết quả: Cac hinh Day la Hinh Day la Hinh Day la Hinh

Các lớp Tamgiac, Hinhtron và Hinhvuong đều kế thừa lớp Hinh,

trong mỗi lớp đều có phương thức hienthi () Trong hàm main () đã tạo ra

ba đối tượng thuộc ba lớp này và một mảng con trỏ thuộc lớp cơ sở, sau đó

cho từng con trỏ của mảng các con trỏ ptr trỏ đến từng đối tượng như câu

lệnh đã nói ở trên

Chúng ta mong đợi là chương trình chạy và hiển thị lên màn hình kết

quả mong muốn là các đối tượng hình được thông báo đúng Chương trình

không những không làm cho chương trình dịch báo lỗi về kiểu dữ liệu địa

chỉ của các đối tượng kế thừa khi cho con-trỏ ptr của lớp kế thừa trỏ đến, mà còn được dịch và thực hiện rất trôi chảy; bởi vì kiểu đữ liệu mà nó kiểm tra có liên quan đến địa chỉ Theo nguyên tắc thì con tré ptr{ i} tro đến các đối tượng của lớp kế thừa là các kiểu dữ liệu tương thích với các con trỏ tới các đối tượng của lớp cơ sở

Bây giờ ta xét đến khi nào thì câu lệnh sau được thực hiện đúng? ptr{ i] ->hienthi();

Phương thức nào được gọi? nó chính là phương thức hienthi () của

lớp cơ sở Với các câu lệnh gán địa chỉ của các biến thuộc lớp kế thừa cho

con trỏ ptr thì các câu lệnh:

ptrfril -> hienthi();

cũng sẽ triệu gọi phương thức hienthi () của lớp cơ sở, kết quả ở ví dụ 6.1 trên cho thấy điều đó

Trên hình 6.2, phương thức hienthi () của lớp cơ sở luôn luôn được

gọi Trình biên dịch đã từ chối nội dung của con trỏ ptr{ i) (dia chi của biến mà nó trỏ đến) và chọn phương thức thành phần phù hợp với kiểu của con trỏ Hình 6.2 cho thấy điều gì đã xây ra khi lớp cơ sở được kế thừa bởi

nhiều lớp với các phương thức cùng tên, và việc triệu gọi cấc phương thức

bằng cách sử dụng các con trỏ nhưng không sử dụng phương thức ảo

Trang 35

pH] Lớp Hinh &abc ptr[0]->hienthi() Ì—ˆ tien hienthi(t ) pt[1] &c SN SG ptr[1]}->hienthi() Hinh tam giác Hinh tròn Hinh vuông , ptr{2} hienthi(){ } hienthi({ } hienthi(){ } &hv ptr[2]->hienthi()

Hình 6.2 Sử dụng con trỏ khi phương thức Hienthi() chưa là phương thức ảo

Để phương thức trở thành phương thức ảo, ta đặt từ khoá virtua1 trước kiểu trả lại của phương thức thì phương thức đó sẽ trở thành phương thức ảo

Chẳng hạn, phương thức ảo hienthi () của lớp cơ sở Hinh là:

virtual void hienthi ()

{

cout<<"\n Day la Hinh";

Ta xét xem vấn để là tại sao khi thêm từ khoá virtua1 vào phương

Trang 36

Class Tamgiac: public Hinh { public: void hienthi () { cout << "\n Day la Tam giac"; } de class Hinhtron: public Hinh { public: i void hienthi () : {_ cout <<"\nHinh tron"; } ` }z class Hinhvuong: public Hinh ( : public: void hienthi() { cout <<"\n Day la Hinh vuong" ; } }z void main() {

Tamgiac abc; Hinhtron c; Hinhvuong hv; Hinh *pf{ 3] ; //mảng con trổ của lớp cơ sở pl 0] = &abc; BÍ l]= &c; Đ[ 2] = &hv; cout<<"\nCac hinh\n"; for(int i=0; i< 3; i++) pl i] ->hienthi(); } Kết quả: Cac hinh

Day la Tam giac Day la Hinh tron Day la Hinh vuong

Trang 37

Các câu lệnh hàm main () ở ví dụ trên thực hiện như hình 6.3, kết quả

hiển thị như mong muốn ptr[0] Lớp Hinh &abc ptr[0}>hienthiQ hienthi0( } ptf1] : &c eee ptr[1]->hienthi() : Hinh tam giác Hinh tròn Hinh vuông ptr[2] TỐ a |„ hienthi( } hienthi(){ } hienthi(){ } &hv / i ptr[2}->hienthi() a Hình 6.3 Sử dụng phương thức ảo

Chúng ta sẽ để cập đến việc chọn lựa phương thức hiển thị của từng đối tượng cụ thể theo hình mũi tên tương ứng trong hình 6.3

Trong hình 6.3 ta thấy, các phương thức thành phần của các lớp kế thừa được gọi chứ không phải phương thức của cơ sở được gọi Ở đây thay đổi nội dung của con trỏ ptr từ các địa chỉ của các đối tượng abc, c và hv Cac thể hiện của phương thức hienthi () được thực hiện bằng câu lệnh:

ptr[ ¡] ->hienthi ();

sẽ thực hiện các phương thức thành phần dựa vào nội dung của con trổ

ptrf i] theo nguyên tắc là trình biên dịch sẽ lựa chọn phương thức theo nội dung của con trỏ (là địa chỉ của đối tượng mà ptr trỏ đến) chứ không phải theo kiểu của con trỏ nữa Hình 6.3 cho biết các lời gọi lệnh hienthi () là

đúng như mong muốn ,

Trang 38

6.2 LIEN KET MUON (Late binding) HAY LIEN KET DONG

(Dynamic binding)

Đầu đọc thông mỉnh có thể làm ta ngạc nhiên khi muốn biết làm thế nào

để trình biên dịch biết được phương thức nào được biên dịch khi chạy chương trình

Khi gặp câu lệnh:

ptrí i] ->hienthi();

trình biên dịch luôn luôn biên dịch một lời gọi đến phương thức hienthi ()

trong lớp cơ sở Nhưng khi phương thức hienthi () của lớp cơ sở được gấn đặc tính là phương thức ảo thì trình biên dịch sẽ không biết lớp nào chứa phương thức này; nó có thể là địa chỉ của một đối tượng của các lớp kế thừa, phiền bản (thể hiện) của phương thức hienthi () Trong thực tế, trình biên dịch không biết làm gì, vì thế nó sắp xếp để quyết định trì hoãn lại cho đến

khi chương trình thực hiện mới xử lý Trong thời gian chương trình đang thực hiện, khi được biết một đối tượng nào đó được trỗ đến bởi con trỏ ptrí i],, thì nó sẽ tiếp cận đến phương thức hienthi () của lớp được gọi

Người ta gọi cơ chế này là sự liên kết muộn hay là liên kết động (Việc lựa

chọn các phương thức theo cách thông thường trong khi biên dịch được gọi là sự liên kết sớm hay liên kết tĩnh) Sự liên kết muộn đồi hỏi một vài khai

báo ban đầu, nhưng cung cấp cho người lập trình sức mạnh và sự mềm dẻo

6.3 LỐP CƠ SỞ ẢO VÀ PHƯƠNG THỨC ẢO THUẦN TŨY

Hãy nhớ lại rằng, lớp Hinh trong chương trình ở ví dụ 6.L chưa hề tạo

một đối nào của lớp Hinh, mà chỉ tạo đối tượng các hình cụ thể như hình tròn, hình tam giác và hình vuông Khi không muốn có một đối tượng có kiểu thuộc lớp cơ sở, ta gọi lớp này là lớp /rừu tượng Lớp trừu tượng chỉ tồn

tại như là lớp cha của các lớp kế thừa mà chúng sẽ sử dụng để tạo các đối

tượng; nó cũng cung cấp một giao diện cho các lớp kế thừa

Trở lại ví dụ 6.1, nếu trong các lớp Tamgiac, Hinhtron hay Hinhvuong

mà không có phương thức hienthi () thì câu lệnh:

Trang 39

ptrf{ i] ->hienthi();

Sẽ triệu gọi phương thức hienthi () của lớp cơ sở Thế nhưng, ta không mong muốn điều này, bởi vì ta muốn hiển thị chính xác đối tượng Vậy phải

làm cách nào? Câu trả lời là thiết lập phương thức hienthi () của lớp cơ sở Hình là phương thức ảo thuần tuý và khai báo như sau:

virtual void hienthi()const = 0;

Ví dụ 6.3 cho thấy, nếu phương thức hienthi () của lớp cơ sở là ảo

thuần tuý thì các phương thức trùng tên của các lớp kế thừa cũng là ảo và

phải có mặt trong lớp kế thừa đó Nếu lớp nào không có thì máy sẽ báo lỗi

khi thực hiện câu lệnh triệu gọi phương thức hienthi () của đối tượng gọi

phương thức này Phương thức ảo thuần tuý không làm gì cả mà chỉ là cái

Trang 40

cout <<"\nDay la Hinh tron"; Me class Hinhvuong: public Hinh ¬ public: void hienthi () { cout <<"\nDay la Hinh vuong"; di void main() { Tamgiac abc; Hinhtron c; Hinhvuong hv; Hinh *ptr{ 3]; ptr[ 0) = sabc; ptrí l1]= &C; ptr[ 2] = khv;

cout<<"\nHien thi cac hinh\n"; for(int i=0; i< 3; i++)

ptr[ i] -> hienthi();

}

Kết quả:

Hien thi cac hinh Day 1a Tam giac

Day la Hinh tron

Day la Hinh vuong

Lớp Hinh được gọi là lớp trừu tượng, vì nó có phương thức ảo thuần

túy Các quy tắc sử dụng lớp trừu tượng như sau:

— Không sử đụng lớp trừu tượng để khai báo biến;

.— Lớp trừu tượng là lớp tổng quát hoá của các lớp kế thừa nó; — Ta có thể khai báo con trỏ có kiểu là một lớp trừu tượng;

— Một phương thức ảo thuần túy được khai báo trong lớp cơ sở phải được khai báo lại trong các lớp kế thừa

Ngày đăng: 24/01/2022, 10:54

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN