Bài giảng Lập trình hướng đối tượng C++ - Chương 6: Tương ứng bội và phương thức ảo

doc 25 trang hoanguyen 21771
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 C++ - Chương 6: Tương ứng bội và phương thức ảo", để 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:

  • docbai_giang_lap_trinh_huong_doi_tuong_c_chuong_6_tuong_ung_boi.doc

Nội dung text: Bài giảng Lập trình hướng đối tượng C++ - Chương 6: Tương ứng bội và phương thức ảo

  1. Chương 6 cout << "\n Lop B " ; Tương ứng bội và phương thức ảo } }; Tương ứng bội và phương thức ảo là công cụ mạnh của C++ class C:public B cho phép tổ chức quản lý các đối tượng khác nhau theo cùng một lược đồ. Một khái niệm khác liên quan là: lớp cơ sở trừu tượng. { Chương này sẽ trình bầy cách sử dụng các công cụ trên để xây dựng public: chương trình quản lý nhiều đối tượng khác nhau theo một lược đồ void xuat() thống nhất. { § 1. Phương thức tĩnh cout << "\n Lop C " ; 1.1. Lời gọi tới phương thức tĩnh } Như đã biết một lớp dẫn xuất được thừa kế các phương thức của }; các lớp cơ sở tiền bối của nó. Ví dụ lớp A là cơ sở của B, lớp B lại Lớp C có 2 lớp cơ sở tiền bối là A , B và C kế thừa các phương là cơ sở của C, thì C có 2 lớp cơ sở tiền bối là B và A. Lớp C được thức của A và B. Do đó một đối tượng của C sẽ có tới 3 phương thừa kế các phương thức của A và B. Các phương thức mà chúng ta thức xuat. Hãy theo rõi các câu lệnh sau: vẫn nói là các phương thức tĩnh. Để tìm hiểu thêm về cách gọi tới các phương thức tĩnh, ta xét ví dụ về các lớp A, B và C như sau: C h ; // h là đối tượng kiểu C class A h.xuat() ; // Gọi tới phương thức h.D::xuat() { h.B::xuat() ; // Gọi tới phương thức h.B::xuat() public: h.A::xuat() ; // Gọi tới phương thức h.A::xuat() void xuat() Các lời gọi phương thức trong ví dụ trên đều xuất phát từ đối tượng h và mọi lời gọi đều xác định rõ phương thức cần gọi. { Bây giờ chúng ta hãy xét các lời gọi không phải từ một biến đối cout << "\n Lop A " ; tượng mà từ một con trỏ. Xét các câu lệnh: } A *p, *q, *r; // p, q, r là con trỏ kiểu A }; A a; // a là đối tượng kiểu A class B:public A B b; // b là đối tượng kiểu B { C c; // c là đối tượng kiểu c public: Chúng ta hãy ghi nhớ mệnh đề sau về con trỏ của các lớp dẫn void xuat() xuất và cơ sở: { 317 318
  2. Phép gán con trỏ: Con trỏ của lớp cơ sở có thể dùng để chứa địa { chỉ các đối tượng của lớp dẫn xuất. p->xuat(); Như vậy cả 3 phép gán sau đều hợp lệ: } p = &a ; Không cần biết tới địa chỉ của đối tượng nào sẽ truyền cho đối q = &b ; con trỏ p, lời gọi trong hàm luôn luôn gọi tới phương thức A::xuat() 319 320 r = &c ; vì con trỏ p kiểu A. Như vậy bốn câu lệnh: hien(&a); Chúng ta tiếp tục xét các lời gọi phương thức từ các con trỏ p, q, hien(&b); r: hien(&c); p->xuat(); hien(&d); q->xuat(); trong hàm main (của chương trình dưới đây) đều gọi tới A::xuat(). r->xuat(); //CT6-01 và hãy lý giải xem phương thức nào (trong các phương thức A::xuat, // Phuong thuc tinh B::xuat và C::xuat) được gọi. Câu trả lời như sau: #include Cả 3 câu lệnh trên đều gọi tới phương thức A::xuat() , vì các con #include trỏ p, q và r đều có kiểu A. #include Như vậy có thể tóm lược cách thức gọi các phương thức tĩnh như #include sau: class A Quy tắc gọi phương thức tĩnh: Lời gọi tới phương thức tĩnh bao { giờ cũng xác định rõ phương thức nào (trong số các phương thức trùng tên của các lớp có quan hệ thừa kế) được gọi: private: int n; 1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào, thì phương thức của lớp đó sẽ được gọi. public: 2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp nào, thì phương A() thức của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ của đối tượng { nào. n=0; } 1.2. Ví dụ A(int n1) Xét 4 lớp A, B, C và D. Lớp B và C có chung lớp cơ sở A. Lớp D { dẫn xuất từ C. Cả 4 lớp đều có phương thức xuat(). Xét hàm: n=n1; void hien(A *p) }
  3. void xuat() } { C(int n1):A(n1) cout xuat(); C():A() } { void main()
  4. { private: A a(1); char ht[25]; B b(2); int sobd; C c(3); float td; D d(4); public: clrscr(); void nhap() hien(&a); { hien(&b); cout > sobd; } cout > td; § 2. Sự hạn chế của phương thức tĩnh } Ví dụ sau cho thấy sự hạn chế của phương thức tĩnh trong việc sử void in() dụng tính thừa kế để phát triển chương trình. { Giả sử cần xây dựng chương trình quản lý thí sinh. Mỗi thí sinh fprintf(stdprn,"\n\nHo ten: %s", ht); đưa vào ba thuộc tính: Họ tên, số báo danh và tổng điểm. Chương fprintf(stdprn,"\nSo bao danh: %d", sobd); trình gồm ba chức năng: Nhập dữ liệu thí sinh, in dữ liệu thí sinh ra fprintf(stdprn,"\nTong diem: %0.1f", td); máy in và xem - in (in họ tên ra màn hình, sau đó lựa chọn hoặc in hoặc không). Chương trình dưới đây sử dụng lớp TS (Thí sinh) đáp } ứng được yêu cầu đặt ra. void xem_in() //CT6-02 { // Han che phuong thuc tinh int ch; // Lop TS cout cout ch = toupper(getch()); #include #include if (ch=='C') class TS this->in(); { }
  5. } ; } void main() void in() { { TS t[100]; TS::in(); int i, n; fprintf(stdprn,"\nDia chi: %s", dc); cout > n; }; for (i=1; i } #include Giả sử Nhà trường muốn quản lý thêm địa chỉ của thí sinh. Vì sự thay đổi ở đây là không nhiều, nên chúng ta không đả động đến lớp #include TS mà xây dựng lớp mới TS2 dẫn xuất từ lớp TS. Trong lớp TS2 #include đưa thêm thuộc tính dc (địa chỉ) và các phương thức nhap, in. Cụ thể class TS lớp TS2 được định nghĩa như sau: { class TS2:public TS private: { char ht[25]; private: int sobd; char dc[30] ; // Dia chi float td; public: public: void nhap() void nhap() { { TS::nhap(); cout << "\nHo ten: " ; cout << "Dia chi: " ; fflush(stdin); gets(ht); fflush(stdin); gets(dc); cout << "So bao danh: " ;
  6. cin >> sobd; TS::nhap(); cout > td; fflush(stdin); gets(dc); } } void in() void in() { { fprintf(stdprn,"\n\nHo ten: %s", ht); TS::in(); fprintf(stdprn,"\nSo bao danh: %d", sobd); fprintf(stdprn,"\nDia chi: %s", dc); fprintf(stdprn,"\nTong diem: %0.1f", td); } } }; void xem_in() void main() { { 327 328 int ch; TS2 t[100]; cout > n; if (ch=='C') for (i=1; i in(); //Goi den TS::in() (Vi this la con tro t[i].nhap(); //kieu TS) for (i=1; i<=n; ++i) } t[i].xem_in(); } ; getch(); class TS2:public TS } { Khi thực hiện chương trình này, chúng ta nhận thấy: Dữ liệu in ra private: vẫn không có địa chỉ. Điều này có thể giải thích như sau: char dc[30] ; // Dia chi Xét câu lệnh (thứ 2 từ dưới lên trong hàm main): public: t[i].xem_in() ; void nhap() Câu lệnh này gọi tới phương thức xem_in của lớp TS2 (vì t[i] là { đối tượng của lớp TS2). Nhưng lớp TS2 không định nghĩa phương
  7. thức xem_in, nên phương thức TS::xem_in() sẽ được gọi tới. Hãy + Hoặc thêm từ khoá virtual vào dòng tiêu đề của phương thức theo rõi phương thức này: bên trong định nghĩa lớp cơ sở A. void xem_in() + Hoặc thêm từ khoá virtual vào dòng tiêu đề bên trong định { nghĩa của tất cả các lớp A, B, C và D. int ch; Ví dụ: cout in(); //Goi den TS::in() (Vi this la con tro kieu TS) virtual void hien_thi() } { Các lệnh đầu của phương thức sẽ in họ tên thí sinh. Nếu chọn có cout in() ; } ; 329 330 sẽ được thực hiện. Mặc dù địa chỉ của t[i] (là đối tượng của lớp TS2) class B : public A được truyền cho con trỏ this, thế nhưng câu lệnh này luôn luôn gọi tới phương thức TS::in(), vì con trỏ this ở đây có kiểu TS và vì in() { là phương thức tĩnh. Kết quả là không in được địa chỉ của thí sinh. Như vậy việc sử dụng các phương thức tĩnh in() (trong các lớp TS void hien_thi() và TS2) đã không đáp ứng được yêu cầu phát triển chương trình. Có { một giải pháp rất đơn giản là: Định nghĩa các phương thức in() trong cout << “\n Đây là lớp B” ; các lớp TS và TS2 như các phương thức ảo (virtual). }; } ; § 3. Phương thức ảo và tương ứng bội class C : public B 3.1. Cách định nghĩa phương thức ảo { Giả sử A là lớp cơ sở, các lớp B, C, D dẫn xuất (trực tiếp hoặc dán tiếp) từ A. Giả sử trong 4 lớp trên đều có các phương thức trùng dòng tiêu đề (trùng kiểu, trùng tên, trùng các đối). Để định nghĩa các void hien_thi() phương thức này là các phương thức ảo, ta chỉ cần: { cout << “\n Đây là lớp C” ;
  8. }; } ; virtual void hien_thi() class D : public A { { cout << “\n Đây là lớp C” ; }; } ; void hien_thi() class D : public A { { cout << “\n Đây là lớp D” ; }; virtual void hien_thi() } ; { cout << “\n Đây là lớp D” ; Cách 2: }; class A } ; { Chú ý: Từ khoá virtual không được đặt bên ngoài định nghĩa lớp. Ví dụ nếu viết như sau là sai (CTBD sẽ báo lỗi). virtual void hien_thi() 331 332 class A { cout << “\n Đây là lớp A” ; { }; } ; virtual void hien_thi() ; class B : public A } ; { virtual void hien_thi() // Sai { virtual void hien_thi() cout << “\n Đây là lớp A” ; { }; cout << “\n Đây là lớp B” ; }; Cần sửa lại như sau: } ; class A class C : public B { {
  9. virtual void hien_thi() ; B b ; // b là biến đối tượng kiểu B } ; C c ; // c là biến đối tượng kiểu C void hien_thi() // Đúng D d ; // d là biến đối tượng kiểu D { Xét lời gọi tới các phương thức ảo hien_thi sau: cout hien_thi() ; // Gọi tới A::hien_thi() 3.2. Quy tắc gọi phương thức ảo p = &b; // p trỏ tới đối tượng b của lớp B Để có sự so sánh với phương thức tĩnh, ta nhắc lại quy tắc gọi p->hien_thi() ; // Gọi tới B::hien_thi() phương thức tĩnh nêu trong §1. p = &c; // p trỏ tới đối tượng c của lớp C 3.2.1. Quy tắc gọi phương thức tĩnh p->hien_thi() ; // Gọi tới C::hien_thi() Lời gọi tới phương thức tĩnh bao giờ cũng xác định rõ phương p = &d; // p trỏ tới đối tượng d của lớp D thức nào (trong số các phương thức trùng tên của các lớp có quan hệ p->hien_thi() ; // Gọi tới D::hien_thi() thừa kế) được gọi: 1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào, thì phương thức của lớp đó sẽ được gọi. 3.3. Tương ứng bội 333 334 2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp nào, thì phương Chúng ta nhận thấy cùng một câu lệnh thức của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ của đối tượng p->hien_thi(); nào. tương ứng với nhiều phương thức khác nhau. Đây chính là tương 3.2.2. Quy tắc gọi phương thức ảo ứng bội. Khả năng này rõ ràng cho phép xử lý nhiều đối tượng khác Phương thức ảo chỉ khác phương thức tĩnh khi được gọi từ một nhau, nhiều công việc, thậm chí nhiều thuật toán khác nhau theo con trỏ (trường hợp 2 nêu trong mục 3.2.1). Lời gọi tới phương thức cùng một cách thức, cùng một lược đồ. Điều này sẽ được minh hoạ ảo từ một con trỏ chưa cho biết rõ phương thức nào (trong số các trong các mục tiếp theo. phương thức ảo trùng tên của các lớp có quan hệ thừa kế) sẽ được gọi. Điều này phụ thuộc vào đối tượng cụ thể mà con trỏ đang trỏ 3.4. Liên kết động tới: Con trỏ đang trỏ tới đối tượng của lớp nào thì phương thức của Có thể so sánh sự khác nhau giữ phương thức tĩnh và phương lớp đó sẽ được gọi. thức ảo trên khía cạnh liên kết một lời gọi với một phương thức. Trở Ví dụ A, B, C và D là các lớp đã định nghĩa trong 3.1. Ta khai lại ví dụ trong 3.2: báo một con trỏ kiểu A và 4 đối tượng: A *p ; // p là con trỏ kiểu A A *p ; // p là con trỏ kiểu A A a ; // a là biến đối tượng kiểu A A a ; // a là biến đối tượng kiểu A B b ; // b là biến đối tượng kiểu B
  10. C c ; // c là biến đối tượng kiểu C + Như đã nói trong §1, C++ cho phép gán địa chỉ đối tượng của D d ; // d là biến đối tượng kiểu D một lớp dẫn xuất cho con trỏ của lớp cơ sở. Như vậy các phép gán sau (xem 3.2) là đúng: Nếu hien_thi() là các phương thức tĩnh, thì dù p chứa địa chỉ của các đối tượng a, b, c hay d, thì lời gọi: A *p ; // p là con trỏ kiểu A p->hien_thi() ; A a ; // a là biến đối tượng kiểu A luôn luôn gọi tới phương thức A::hien_thi() B b ; // b là biến đối tượng kiểu B C c ; // c là biến đối tượng kiểu C Như vậy một lời gọi (xuất phát từ con trỏ) tới phương thức tĩnh luôn luôn liên kết với một phương thức cố định và sự liên kết này D d ; // d là biến đối tượng kiểu D xác định trong quá trình biên dịch chương trình. p = &a; // p và a cùng lớp A Cũng với lời gọi: p = &b; // p là con trỏ lớp cơ sở, b là đối tượng lớp dẫn xuất p->hien_thi() ; p = &c; // p là con trỏ lớp cơ sở, c là đối tượng lớp dẫn xuất như trên, nhưng nếu hien_thi() là các phương thức ảo, thì lời gọi này p = &d; // p là con trỏ lớp cơ sở, d là đối tượng lớp dẫn xuất không liên kết cứng với một phương thức cụ thể nào. Phương thức + Tuy nhiên cần chú ý là: Không cho phép gán địa chỉ đối tượng mà nó liên kết (gọi tới) còn chưa xác định trong giai đoạn dịch của lớp cở sở cho con trỏ của lớp dẫn xuất. Như vậy ví dụ sau là sai: chương trình. Lời gọi này sẽ: B *q ; + liên kết với A::hien_thi() , nếu p chứa địa chỉ đối tượng lớp A a ; A q = &a; + liên kết với B::hien_thi() , nếu p chứa địa chỉ đối tượng lớp B Sai vì: Gán địa chỉ đối tượng của lớp cơ sở A cho con trỏ của lớp dẫn xuất B + liên kết với C::hien_thi() , nếu p chứa địa chỉ đối tượng lớp C 3.6. Ví dụ + liên kết với D::hien_thi() , nếu p chứa địa chỉ đối tượng lớp Ta sửa chương trình trong §1 bằng cách định nghĩa các phương D thức xuat() là ảo. Khi đó bốn câu lệnh: Như vậy một lời gọi (xuất phát từ con trỏ) tới phương thức ảo hien(&a); 335 336 không liên kết với một phương thức cố định, mà tuỳ thuộc vào nội hien(&b); dung con trỏ. Đó là sự liên kết động và phương thức được liên kết (được gọi) thay đổi mỗi khi có sự thay đổi nội dung con trỏ trong hien(&c); quá trình chạy chương trình. hien(&d); trong hàm main (của chương trình dưới đây) sẽ lần lượt gọi tới 4 3.5. Quy tắc gán địa chỉ đối tượng cho con trỏ lớp cơ sở phương thức khác nhau: A::xuat()
  11. B::xuat() return n; C::xuat() } D::xuat() }; //CT6-01B class B:public A // Phuong thuc ảo và tương ứng bội { #include public: #include B():A() #include { #include } class A B(int n1):A(n1) { { private: } int n; void xuat() public: { A() cout << "\nLop B: "<<getN(); { } n=0; }; } class C:public A A(int n1) { { public: n=n1; C():A() } { virtual void xuat() } { C(int n1):A(n1) cout << "\nLop A: "<< n; 337 338 } { int getN() } { void xuat()
  12. { hien(&b); cout xuat(); chúng như các phương thức ảo. Chương trình khi đó sẽ như sau: } //CT6-03B void main() // Sự linh hoạt của phương thức ảo { // Lop TS TS2 A a(1); #include B b(2); #include C c(3); #include D d(4); #include 339 340 clrscr(); class TS hien(&a);
  13. { this->in(); // Vì in() là phương thức ảo nên private: //có thể gọi đến TS::in() hoặc TS2::in() char ht[25]; } int sobd; } ; float td; class TS2:public TS public: { void nhap() private: { char dc[30] ; // Dia chi cout > sobd; TS::nhap(); cout > td; } } void in() virtual void in() { { TS::in(); fprintf(stdprn,"\n\nHo ten: %s", ht); fprintf(stdprn,"\nDia chi: %s", dc); fprintf(stdprn,"\nSo bao danh: %d", sobd); } fprintf(stdprn,"\nTong diem: %0.1f", td); }; } void xem_in() void main() { { int ch; TS2 t[100]; cout > n; if (ch=='C') for (i=1; i<=n; ++i) 341 342
  14. t[i].nhap(); Như vậy việc sử dụng các phương thức tĩnh in() (trong các lớp TS và for (i=1; i in(); // Vì in() là phương thức ảo nên class A //có thể gọi đến TS::in() hoặc TS2::in() { } public: Các lệnh đầu của phương thức sẽ in họ tên thí sinh. Nếu chọn Có virtual void nhap() = 0 ; (bấm phím C), thì câu lệnh: virtual void xuat() = 0 ; this->in() ; void chuong(); sẽ được thực hiện. Địa chỉ của t[i] (là đối tượng của lớp TS2) được } ; truyền cho con trỏ this (của lớp cơ sở TS). Vì in() là phương thức ảo và vì this đang trỏ tới đối tượng t[i] của lớp TS2, nên câu lệnh này Trong ví dụ trên, thì A là lớp cơ sở trừu tượng. Các phương thức gọi tới phương thức TS2::in(). Trong phương thức TS2::in() có in địa nhap và xuat được khai báo là các lớp ảo thuần tuý (bằng cách gán chỉ của thí sinh. số 0 cho chúng thay cho việc cài đặt các phương thức này). Phương 343 344
  15. thức chuong() là một phương thức bình thường và sẽ phải có một + Xuất (đem bán) một con vật (hoặc chó, hoặc mèo). định nghĩa ở đâu đó cho phương thức này. + Thống kê các con vật đang nuôi trong 20 ô. Không có đối tượng nào của một lớp trừu tượng lại có thể được Chương trình được tổ chức như sau: phát sinh. Tuy nhiên các con trỏ và các biến tham chiếu đến các đối tượng của lớp trừu tượng thì vẫn hợp lệ. Bất kỳ lớp nào dẫn xuất từ + Trước tiên định nghĩa lớp CON_VAT là lớp cơ sở ảo. Lớp này một lớp cớ sở trừu tượng phải định nghĩa lại tất cả các phương thức có một thuộc tính là tên con vật và một phương thức ảo dùng để thuần ảo mà nó thừa hưởng, hoặc bằng các phương thức ảo thuần xưng tên. tuý, hoặc bằng những định nghĩa thực sự. Ví dụ: + Hai lớp là CON_MEO và CON_CHO được dẫn xuất từ lớp class B : public A CON_VAT { + Cuối cùng là lớp DS_CON_VAT (Danh sách con vật) dùng để quản lý chung cả mèo và chó. Lớp này có 3 thuộc tính là: số con vật public: cực đại (chính bằng số ô), số con vật đang nuôi và một mảng con trỏ virtual void nhap() = 0 ; kiểu CON_VAT. Mỗi phần tử mảng sẽ chứa địa chỉ của một đối virtual void xuat() tượng kiểu CON_MEO hoặc CON_CHO. { Lớp sẽ có 3 phương thức để thực hiện 3 chức năng nêu trên của chương trình. Nội dung chương trình như sau: // Các câu lệnh //CT6-04 } // Lop co so truu tuong } ; // Lop CON_VAT Theo ý nghĩa về hướng đối tượng, ta vẫn có thể có một lớp trừu #include tượng mà không nhất thiết phải chứa đựng những phương thức thuần #include tuý ảo. #include Một cách tổng quát mà nói thì bất kỳ lớp nào mà nó chỉ được #include dùng làm cơ sở cho những lớp khác đều có thể được gọi là “lớp trừu tượng”. Một cách dễ dàng để nhận biết một lớp trừu tượng là xem có #include dùng lớp đó để khai báo các đối tượng hay không? . Nếu không thì class CON_VAT đó là lớp cơ sở trừu tượng. { protected: 5.2. Ví dụ char *ten; Giả sử có 20 ô, mỗi ô có thể nuôi một con chó hoặc một con mèo. Yêu cầu xây dựng chương trình gồm các chức năng: public: CON_VAT() + Nhập một con vật mới mua (hoặc chó, hoặc mèo) vào ô rỗng đầu tiên. {
  16. ten = NULL; CON_CHO() : CON_VAT() } { CON_VAT(char *ten1) } { 345 346 CON_CHO(char *ten1) : CON_VAT(ten1) ten = strdup(ten1); { } } virtual void xung_ten() virtual void xung_ten() { { } cout << "\nToi la chu cho: " << ten ; } ; } class CON_MEO:public CON_VAT }; { class DS_CON_VAT // Danh sach con vat public: { CON_MEO() : CON_VAT() private: { int max_so_con_vat; } int so_con_vat; CON_MEO(char *ten1) : CON_VAT(ten1) CON_VAT h ; { public: } DS_CON_VAT(int max); ~DS_CON_VAT(); virtual void xung_ten() int nhap(CON_VAT *c); { CON_VAT* xuat(int n); cout << "\nToi la chu meo: " << ten ; void thong_ke(); } } ; }; DS_CON_VAT::DS_CON_VAT(int max) class CON_CHO:public CON_VAT { { max_so_con_vat = max; public: so_con_vat = 0;
  17. h = new CON_VAT*[max]; h[n]=NULL; for (int i=0; i xung_ten(); int i=0; } while (h[i]!=NULL) ++i; } h[i]=c; CON_CHO c1("MUC"); so_con_vat++ ; CON_CHO c2("VEN"); return (i+1); CON_CHO c3("LAI"); } CON_CHO c4("NHAT"); CON_VAT* DS_CON_VAT::xuat(int n) CON_CHO c5("BONG"); { CON_MEO m1("MUOP"); if (n max_so_con_vat) CON_MEO m2("DEN"); return NULL ; CON_MEO m3("TRANG"); n ; CON_MEO m4("TAM THE"); if (h[n]) CON_MEO m5("VANG"); { void main() CON_VAT *c = h[n]; {
  18. DS_CON_VAT d(20); Bây giờ nếu định nghĩa lại phương thức xung_ten như sau: clrscr(); virtual void xung_ten() = 0 ; d.nhap(&c1); thì nó trở thành phương thức thuần ảo và C++ sẽ quan niệm lớp int im2 = d.nhap(&m2); CON_VAT là lớp trừu tượng. Khi đó câu lệnh khai báo: d.nhap(&c3); CON_VAT cv(“Con vat chung”); d.nhap(&m1); sẽ bị C++ bắt lỗi với thông báo: int ic4 = d.nhap(&c4); 349 350 Cannot create instance of abstruct class ‘CON_VAT’ d.nhap(&c5); d.nhap(&m5); § 6. Sử dụng tương ứng bội và phương thức ảo d.nhap(&c2); 6.1. Chiến lược sử dụng tương ứng bội d.nhap(&m3); Tương ứng bội cho phép xét các vấn đề khác nhau, các đối tượng d.thong_ke(); khác nhau, các phương pháp khác nhau, các cách giải quyết khác d.xuat(im2); nhau theo cùng một lược đồ chung. d.xuat(ic4); Các bước áp dụng tương ứng bội có thể tổng kết lại như sau: d.thong_ke(); 1. Xây dựng lớp cơ sở trừu tượng bao gồm những thuộc tính getch(); chung nhất của các thực thể cần quản lý. Đưa vào các phương thức } ảo hay thuần ảo dùng để xây dựng các nhóm phương thức ảo cho các lớp dẫn xuất sau này. Mỗi nhóm phương thức ảo sẽ thực hiện một Chú ý: Theo quan điểm chung về cách thức sử dụng, thì lớp chức năng nào đó trên các lớp. CON_VAT là lớp cơ sở trừu tượng. Tuy nhiên theo quan điểm của C++ thì lớp này chưa phải là lớp cơ sở trừu tượng, vì trong lớp 2. Xây dựng các lớp dẫn xuất bắt đầu từ lớp cơ sở ảo. Số mức dẫn không có các phương thức thuần tuý ảo. Phương thức xung_ten: xuất là không hạn chế. Các lớp dẫn xuất sẽ mô tả các đối tượng cụ thể cần quản lý. virtual void xung_ten() 3. Xây dựng các phương thức ảo trong các dẫn xuất. Các phương { thức này tạo thành các nhóm phương thức ảo trong sơ đồ các lớp có } quan hệ thừa kế. là phương thức ảo, được định nghĩa đầy đủ , mặc dù thân của nó là 4. Xây dựng lớp quản lý các đối tượng. Dữ liệu của lớp này là rỗng. một dẫy con trỏ của lớp cơ sở trừu tượng ban đầu. Các con trỏ này Do vậy khai báo: có thể chứa địa chỉ đối tượng của các lớp dẫn xuất. Do vậy có thể CON_VAT cv(“Con vat chung”); dùng các con trỏ này để thực hiện các thao tác trên các đối tượng của bất kỳ lớp dẫn xuất nào. vẫn được C++ chấp nhận.
  19. 6.2. Ví dụ { Chương trình quản lý các con vật trong §5 là một ví dụ về cách sử mau = m; dụng tương ứng bội. Dưới đây là một ví dụ khác. Giả sử có 4 hình } vẽ: Đoạn thẳng, hình tròn, hình chữ nhật và hình vuông. Bốn hình getmau() cho hiện thẳng hàng trên màn hình tạo thành một bức tranh. Nếu { thay đổi thứ tự các hình sẽ nhận được các bức tranh khác nhau. Chương trình dưới đây sẽ cho hiện tất cả các bức tranh khác nhau. return mau; Chương trình được tổ chức theo các bước nêu trong 6.1: } + Lớp cơ sở trừu tượng là lớp HINH (hình) gồm một thuộc tính virtual void draw(int x, int y) = 0; mau (mầu) và một phương thức ảo thuần tuý: }; virtual void draw(int x, int y) = 0 ; class DTHANG : public HINH + Các lớp dẫn xuất trực tiếp từ lớp hình là : DTHANG , HTRON351 352 { và CHUNHAT. private: + Lớp VUONG dẫn xuất từ lớp CHUNHAT. int dodai; + Lớp quản lý chung là lớp picture có thuộc tính là một mảng con public: trỏ kiểu HINH gồm 4 phần tử dùng để chứa địa chỉ 4 đối tượng: DTHANG(int d, int m):HINH(m) DTHANG, HTRON, CHUNHAT và VUONG. Sử dụng phương { thức draw gọi từ 4 phần tử mảng nói trên sẽ nhận được một bức tranh. Bằng cách hoán vị các phần tử này, sẽ nhận được tất cả các dodai = d ; bức tranh khác nhau. } //CT6-05 virtual void draw(int x, int y) // Lop co so truu tuong { // Lop hinh hoc setcolor(getmau()) ; #include line(x,y,x+dodai,y); #include } class HINH }; { class CHUNHAT: public HINH private: { int mau; private: public: int rong, cao; HINH(int m) public:
  20. CHUNHAT(int r, int c, int m):HINH(m) virtual void draw(int x, int y) { { rong = r; cao = c; setcolor(getmau()) ; } circle(x+bk,y+bk,bk); virtual void draw(int x, int y ) setfillstyle(1,getmau()); { floodfill(x + bk, y + bk,getmau()); setcolor(getmau()) ; } }; rectangle(x,y,x+rong,y+cao); class picture setfillstyle(1,getmau()); { floodfill(x+rong/2,y+cao/2, getmau() ); private: } HINH *h[4]; 353 354 }; public: class VUONG : public CHUNHAT picture(HINH *h0,HINH *h1,HINH *h2,HINH *h3) { { public: h[0]=h0; VUONG(int a, int m): CHUNHAT(a,a,m) h[1]=h1; { h[2]=h2; } h[3]=h3; }; } class HTRON: public HINH void paint(int *k); { void listpaint(); private: } ; int bk; //Ban kinh void picture::paint(int *k) public: { HTRON(int bk1, int m):HINH(m) for (int i=0; i draw(10+i*150, 200); bk = bk1; } } void picture::listpaint()
  21. { pic.listpaint(); int k[4],i1,i2,i3,i4; getch(); for (i1=0;i1<4;++i1) closegraph(); for (i2=0;i2<4;++i2) } if (i2!=i1) for (i3=0;i3<4;++i3) § 7. Xử lý các thuật toán khác nhau if (i3!=i2 && i3!=i1) Có thể sử dụng tương ứng bội để tổ chức thực hiện các thuật toán for (i4=0;i4<4;++i4) khác nhau trên cùng một bài toán như sau: if (i4!=i3 && i4!=i2 && i4!=i1) + Lớp cơ sở trừu tượng sẽ chứa dữ liệu bài toán và một phương { thức ảo. k[0]=i1;k[1]=i2; + Mỗi lớp dẫn xuất ứng với một thuật toán cụ thể. Phương thức ảo của lớp dẫn xuất sẽ thực hiện một thuật toán cụ thể. k[2]=i3;k[3]=i4; + Sử dụng một mảng con trỏ của lớp cơ sở và gán cho mỗi phần paint(k); tử mảng địa chỉ của một đối tượng của lớp dẫn xuất. Sau đó dùng getch(); 355 356các phần tử mảng con trỏ để gọi tới các phương thức ảo. Bằng cách cleardevice(); đó sẽ thực hiện cùng một bài toán theo các thuật toán khác nhau và dễ dàng so sánh hiêụ quả của các thuật toán. } Ví dụ sau minh hoạ việc thực hiện bài toán sắp xếp dẫy số nguyên } theo thứ tự tăng bằng cách dùng đồng thời 3 thuật toán: Thuật toán DTHANG dt(120,14); lựa chọn (Select_Sort), thuật toán sắp xếp nhanh (Quick_Sort) và thuật toán vun đống (Heap_Sort). Chương trình gồm 4 lớp: HTRON ht(60,RED); + Lớp cơ sở trừu tượng: CHUNHAT cn(120,100,MAGENTA); class sort VUONG v(120,CYAN); { } ; protected: void main() int *a; { void hoan_vi(long i, long j) ; int mh=0,mode=0; public: initgraph(&mh,&mode,""); virtual void sapxep(int *a1, long n) ; picture pic(&dt,&ht,&cn,&v); } ;
  22. Lớp này gồm: int *a; - Một thành phần dữ liệu là con trỏ a trỏ tới một vùng nhớ chứa void hoan_vi(long i, long j) dẫy số nguyên cần sắp xếp. { - Phương thức hoan_vi(i,j) dùng để hoán vị các phần tử a[i] và int tg = a[i]; a[j]. Phương thức này được dùng trong 3 lớp dẫn xuất bên dưới. a[i] = a[j]; - Phương thức ảo sapxep(a1,n) dùng để sắp xếp dẫy n số nguyên chứa trong mảng a1. a[j] = tg; + Ba lớp dẫn xuất là: SELECT_SORT, QUICK_SORT và } HEAP_SORT. Mỗi lớp đều có phương thức ảo: public: virtual void sapxep(int *a1, long n) ; virtual void sapxep(int *a1, long n) để thực hiện hiện việc sắp xếp theo theo một thuật toán cụ thể. { + Trong hàm main() sẽ tạo ra một dẫy 30000 số nguyên một cách a = a1; ngẫu nhiên, sau đó lần lượt sử dụng 3 thuật toán sắp xếp để so sánh. } Kết quả như sau: } ; Thời gian sắp xếp theo thuật toán Select sort là: 19.20 giây class select_sort : public sort Thời gian sắp xếp theo thuật toán Quick sort là: 0.11 giây { Thời gian sắp xếp theo thuật toán Heap sort là: 0.44 giây 357 358 public: Nội dung chương trình như sau: virtual void sapxep(int *a1, long n) ; //CT6-06 } ; // Lop co so truu tuong void select_sort::sapxep(int *a1, long n) // Lop sort #include { #include long i,j,r; #include sort::sapxep(a1,n); #include for (i=1; i { #include r=i; class sort for (j=i+1; j<=n; ++j) { if(a[j] < a[r]) r = j; protected: if(r!=i) hoan_vi(i,r);
  23. } { } sort::sapxep(a1,n); class quick_sort : public sort q_sort(1,n); { } private: class heap_sort : public sort void q_sort(long l, long r); { public: private: virtual void sapxep(int *a1, long n) ; void shift(long i, long n); } ; public: void quick_sort::q_sort(long l, long r) virtual void sapxep(int *a1, long n) ; { } ; int x; void heap_sort::shift(long i, long n) long i,j; { if (l n) return; do 359 360 { if (l==n) ++i; j; { while (i x) j ; return; if (i a[r]) hoan_vi(l,j); k = l; q_sort(l,j-1); else q_sort(j+1,r); k = r; } if (a[i]>=a[k]) } return; void quick_sort::sapxep(int *a1, long n) else
  24. { puts("\nLoi BN"); hoan_vi(i,k); getch(); shift(k,n); exit(0); } } } sort *s[3]; void heap_sort::sapxep(int *a1, long n) select_sort ss; { quick_sort qs; long i; heap_sort hs; sort::sapxep(a1,n); s[0]=&ss; s[1]=&qs; s[2]=&hs; /* Tao dong */ clrscr(); for (i=n/2 ; i>=1; i) shift(i,n); for (k=0; k =2; i) srand(5000); { for (i=1;i sapxep(a,n); 361 362 } gettime(&t2); void main() tg = (t2.ti_sec - t1.ti_sec)*100 + t2.ti_hund - t1.ti_hund ; { sec = tg / 100; long i,n; hund = tg % 100; struct time t1,t2; printf("\n Sap xep %d %d %d %d %d",k+1, int *a, k, tg, sec, hund; t2.ti_sec,t2.ti_hund,t1.ti_sec,t1.ti_hund); n=30000; printf("\n Sap xep %d Thoi gian %d sec %d hund", a=(int*) malloc((n+1)*sizeof(int)); k+1,sec,hund); if (a==NULL) } { getch(); }