Chương 6 Tương ứng bội và phương thức ảo

Chia sẻ: Men Men | Ngày: | Loại File: DOC | Số trang:25

0
135
lượt xem
26
download

Chương 6 Tương ứng bội và phương thức ảo

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Tham khảo tài liệu 'chương 6 tương ứng bội và phương thức ảo', công nghệ thông tin, kỹ thuật lập trình phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Chủ đề:
Lưu

Nội dung Text: Chương 6 Tương ứng bội và phương thức ảo

  1. Chương 6 void xuat() Tương ứng bội và phương thức ảo { cout
  2. Chúng ta hãy ghi nhớ mệnh đề sau về con trỏ của các lớp 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ẫn xuất và cơ sở: D dẫn xuất từ C. Cả 4 lớp đều có phương thức xuat(). Xét hàm: Phép gán con trỏ: Con trỏ của lớp cơ sở có thể dùng để chứa void hien(A *p) địa chỉ các đối tượng của lớp dẫn xuất. { Như vậy cả 3 phép gán sau đều hợp lệ: p->xuat(); p = &a ; } q = &b ; Không cần biết tới địa chỉ của đối tượng nào sẽ truyền cho đối r = &c ; con trỏ p, lời gọi trong hàm luôn luôn gọi tới phương thức 319 320 A::xuat() vì con trỏ p kiểu A. Như vậy bốn câu lệnh: 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, hien(&a); q, r: hien(&b); p->xuat(); hien(&c); q->xuat(); hien(&d); r->xuat(); trong hàm main (của chương trình dưới đây) đều gọi tới A::xuat(). và hãy lý giải xem phương thức nào (trong các phương thức //CT6-01 A::xuat, B::xuat và C::xuat) được gọi. Câu trả lời như sau: // Phuong thuc tinh Cả 3 câu lệnh trên đều gọi tới phương thức A::xuat() , vì các #include con 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 #include như sau: #include Quy tắc gọi phương thức tĩnh: Lời gọi tới phương thức tĩnh class A 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: { 1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào, thì private: phương thức của lớp đó sẽ được gọi. int n; 2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp nào, thì public: phương thức của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ A() của đối tượng nào. { n=0; 1.2. Ví dụ } A(int n1)
  3. { C():A() n=n1; { } } void xuat() C(int n1):A(n1) { cout
  4. } #include class TS void main() { { 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 void in() sử 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 máy in và xem - in (in họ tên ra màn hình, sau đó lựa chọn hoặc fprintf(stdprn,"\nTong diem: %0.1f", td); 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
  5. if (ch=='C') TS::nhap(); this->in(); cout n; }; for (i=1; i
  6. cout 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
  7. Xét câu lệnh (thứ 2 từ dưới lên trong hàm main): 3.1. Cách định nghĩa phương thức ảo t[i].xem_in() ; 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 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à dán tiếp) từ A. Giả sử trong 4 lớp trên đều có các phương thức đối tượng của lớp TS2). Nhưng lớp TS2 không định nghĩa phương trùng dòng tiêu đề (trùng kiểu, trùng tên, trùng các đối). Để định thức xem_in, nên phương thức TS::xem_in() sẽ được gọi tới. Hãy nghĩa các phương thức này là các phương thức ảo, ta chỉ cần: theo rõi phương thức này: + Hoặc thêm từ khoá virtual vào dòng tiêu đề của phương thức void xem_in() bên trong định nghĩa lớp cơ sở A. { + 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; cout
  8. { { ... cout
  9. }; đang trỏ tới: Con trỏ đang trỏ tới đối tượng của lớp nào thì phương thức của lớp đó sẽ được gọi. Cần sửa lại như sau: Ví dụ A, B, C và D là các lớp đã định nghĩa trong 3.1. Ta khai class A báo một con trỏ kiểu A và 4 đối tượng: { A *p ; // p là con trỏ kiểu A ... A a ; // a là biến đối tượng kiểu A 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 p->hien_thi() ; // Gọi tới D::hien_thi() hệ 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ì Chúng ta nhận thấy cùng một câu lệnh phương thức của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ p->hien_thi(); của đối tượng 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 Phương thức ảo chỉ khác phương thức tĩnh khi được gọi từ một khác nhau, nhiều công việc, thậm chí nhiều thuật toán khác nhau con trỏ (trường hợp 2 nêu trong mục 3.2.1). Lời gọi tới phương theo cùng một cách thức, cùng một lược đồ. Điều này sẽ được thức ảo từ một con trỏ chưa cho biết rõ phương thức nào (trong số minh hoạ trong các mục tiếp theo. các 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ỏ 3.4. Liên kết động
  10. Có thể so sánh sự khác nhau giữ phương thức tĩnh và phương dung con trỏ. Đó là sự liên kết động và phương thức được liên kết 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. (được gọi) thay đổi mỗi khi có sự thay đổi nội dung con trỏ trong Trở lại ví dụ trong 3.2: quá trình chạy chương trình. A *p ; // p là con trỏ kiểu A 3.5. Quy tắc gán địa chỉ đối tượng cho con trỏ lớp cơ sở A a ; // a là biến đối tượng kiểu A + Như đã nói trong §1, C++ cho phép gán địa chỉ đối tượng của B b ; // b là biến đối tượng kiểu B một lớp dẫn xuất cho con trỏ của lớp cơ sở. Như vậy các phép C c ; // c là biến đối tượng kiểu C gán sau (xem 3.2) là đúng: D d ; // d là biến đối tượng kiểu D A *p ; // p là con trỏ kiểu A Nếu hien_thi() là các phương thức tĩnh, thì dù p chứa địa chỉ của A a ; // a là biến đối tượng kiểu A các đối tượng a, b, c hay d, thì lời gọi: B b ; // b là biến đối tượng kiểu B p->hien_thi() ; C c ; // c là biến đối tượng kiểu C luôn luôn gọi tới phương thức A::hien_thi() D d ; // d là biến đối tượng kiểu D Như vậy một lời gọi (xuất phát từ con trỏ) tới phương thức p = &a; // p và a cùng lớp A 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 p = &b; // p là con trỏ lớp cơ sở, b là đối tượng lớp dẫn này xác định trong quá trình biên dịch chương trình. xuất Cũng với lời gọi: p = &c; // p là con trỏ lớp cơ sở, c là đối tượng lớp dẫn p->hien_thi() ; xuất như trên, nhưng nếu hien_thi() là các phương thức ảo, thì lời gọi p = &d; // p là con trỏ lớp cơ sở, d là đối tượng lớp dẫn này không liên kết cứng với một phương thức cụ thể nào. Phương xuất thức mà nó liên kết (gọi tới) còn chưa xác định trong giai đoạn + Tuy nhiên cần chú ý là: Không cho phép gán địa chỉ đối dịch chương trình. Lời gọi này sẽ: tượng của lớp cở sở cho con trỏ của lớp dẫn xuất. Như vậy ví dụ + liên kết với A::hien_thi() , nếu p chứa địa chỉ đối tượng sau là sai: lớp A B *q ; + liên kết với B::hien_thi() , nếu p chứa địa chỉ đối tượng A a; lớp B q = &a; + liên kết với C::hien_thi() , nếu p chứa địa chỉ đối tượng lớp C 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 D::hien_thi() , nếu p chứa địa chỉ đối tượng lớp D 3.6. Ví dụ Như vậy một lời gọi (xuất phát từ con trỏ) tới phương thức ảo Ta sửa chương trình trong §1 bằng cách định nghĩa các phương không liên kết với một phương thức cố định, mà tuỳ thuộc vào nội thức xuat() là ảo. Khi đó bốn câu lệnh: 335 336
  11. hien(&a); } hien(&b); virtual void xuat() hien(&c); { hien(&d); cout
  12. { { } A a(1); B b(2); C(int n1):A(n1) 337 338 C c(3); { D d(4); } clrscr(); void xuat() hien(&a); { hien(&b); cout
  13. // Sự linh hoạt của phương thức ảo void xem_in() // Lop TS TS2 { #include int ch; #include cout
  14. { Các lệnh đầu của phương thức sẽ in họ tên thí sinh. Nếu chọn TS2 t[100]; Có (bấm phím C), thì câu lệnh: int i, n; this->in() ; cout > n; thức ảo và vì this đang trỏ tới đối tượng t[i] của lớp TS2, nên câu for (i=1; i
  15. virtual void nhap() = 0 ; trừu tượng”. Một cách dễ dàng để nhận biết một lớp trừu tượng virtual void xuat() = 0 ; là xem có dùng lớp đó để khai báo các đối tượng hay không? . Nếu không thì đó là lớp cơ sở trừu tượng. void chuong(); }; 5.2. Ví dụ Trong ví dụ trên, thì A là lớp cơ sở trừu tượng. Các phương Giả sử có 20 ô, mỗi ô có thể nuôi một con chó hoặc một con thức nhap và xuat được khai báo là các lớp ảo thuần tuý (bằng mèo. Yêu cầu xây dựng chương trình gồm các chức năng: cách gán số 0 cho chúng thay cho việc cài đặt các phương thức + Nhập một con vật mới mua (hoặc chó, hoặc mèo) vào ô rỗng này). Phương thức chuong() là một phương thức bình thường và đầu tiên. sẽ phải có một định nghĩa ở đâu đó cho phương thức này. + Xuất (đem bán) một con vật (hoặc chó, hoặc mèo). Không có đối tượng nào của một lớp trừu tượng lại có thể 343 344 + Thống kê các con vật đang nuôi trong 20 ô. được phát sinh. Tuy nhiên các con trỏ và các biến tham chiếu đến Chương trình được tổ chức như sau: 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ừ một lớp cớ sở trừu tượng phải định nghĩa lại tất cả + Trước tiên định nghĩa lớp CON_VAT là lớp cơ sở ảo. Lớp các phương thức thuần ảo mà nó thừa hưởng, hoặc bằng các này có một thuộc tính là tên con vật và một phương thức ảo dùng phương thức ảo thuần tuý, hoặc bằng những định nghĩa thực sự. để xưng tên. 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 public: vật cực đại (chính bằng số ô), số con vật đang nuôi và một mảng virtual void nhap() = 0 ; con trỏ kiểu CON_VAT. Mỗi phần tử mảng sẽ chứa địa chỉ của virtual void xuat() một đối 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ác câu lệnh của chương trình. Nội dung chương trình như sau: //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 #include trừu tượng mà không nhất thiết phải chứa đựng những phương #include thức thuần 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 dùng làm cơ sở cho những lớp khác đều có thể được gọi là “lớp #include
  16. #include { class CON_VAT cout
  17. int nhap(CON_VAT *c); CON_VAT* DS_CON_VAT::xuat(int n) CON_VAT* xuat(int n); { void thong_ke(); if (n max_so_con_vat) }; return NULL ; DS_CON_VAT::DS_CON_VAT(int max) --n ; { if (h[n]) max_so_con_vat = max; { so_con_vat = 0; CON_VAT *c = h[n]; h = new CON_VAT*[max]; h[n]=NULL; for (int i=0; i
  18. CON_CHO c5("BONG"); virtual void xung_ten() CON_MEO m1("MUOP"); { CON_MEO m2("DEN"); } CON_MEO m3("TRANG"); là phương thức ảo, được định nghĩa đầy đủ , mặc dù thân của nó là rỗng. CON_MEO m4("TAM THE"); Do vậy khai báo: CON_MEO m5("VANG"); CON_VAT cv(“Con vat chung”); void main() vẫn được C++ chấp nhận. { Bây giờ nếu định nghĩa lại phương thức xung_ten như sau: DS_CON_VAT d(20); virtual void xung_ten() = 0 ; clrscr(); thì nó trở thành phương thức thuần ảo và C++ sẽ quan niệm lớp d.nhap(&c1); CON_VAT là lớp trừu tượng. Khi đó câu lệnh khai báo: int im2 = d.nhap(&m2); CON_VAT cv(“Con vat chung”); d.nhap(&c3); sẽ bị C++ bắt lỗi với thông báo: d.nhap(&m1); Cannot create instance of abstruct class ‘CON_VAT’ int ic4 = d.nhap(&c4); 349 350 d.nhap(&c5); § 6. Sử dụng tương ứng bội và phương thức ảo d.nhap(&m5); 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 d.thong_ke(); tượng khác nhau, các phương pháp khác nhau, các cách giải quyết d.xuat(im2); khác 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 Chú ý: Theo quan điểm chung về cách thức sử dụng, thì lớp hiện một 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 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 C++ thì lớp này chưa phải là lớp cơ sở trừu tượng, vì trong lớp dẫn xuất là không hạn chế. Các lớp dẫn xuất sẽ mô tả các đối không có các phương thức thuần tuý ảo. Phương thức xung_ten: tượng cụ thể cần quản lý.
  19. 3. Xây dựng các phương thức ảo trong các dẫn xuất. Các #include phương thức này tạo thành các nhóm phương thức ảo trong sơ đồ #include các lớp có quan hệ thừa kế. class HINH 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à { một dẫy con trỏ của lớp cơ sở trừu tượng ban đầu. Các con trỏ này có thể chứa địa chỉ đối tượng của các lớp dẫn xuất. Do vậy private: có thể dùng các con trỏ này để thực hiện các thao tác trên các đối int mau; tượng của bất kỳ lớp dẫn xuất nào. public: HINH(int m) 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 mau = m; sử 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 cho hiện thẳng hàng trên màn hình tạo thành một bức tranh. getmau() 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 return mau; nhau. 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 , 351 352 { HTRON 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 public: con trỏ kiểu HINH gồm 4 phần tử dùng để chứa địa chỉ 4 đối tượng: DTHANG, HTRON, CHUNHAT và VUONG. Sử dụng DTHANG(int d, int m):HINH(m) 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 dodai = d ; tất cả các 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()) ;
  20. line(x,y,x+dodai,y); { } private: }; int bk; //Ban kinh class CHUNHAT: public HINH public: { HTRON(int bk1, int m):HINH(m) private: { int rong, cao; bk = bk1; public: } virtual void draw(int x, int y) CHUNHAT(int r, int c, int m):HINH(m) { { setcolor(getmau()) ; rong = r; cao = c; circle(x+bk,y+bk,bk); } setfillstyle(1,getmau()); virtual void draw(int x, int y ) 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; } } }; void paint(int *k); class HTRON: public HINH

CÓ THỂ BẠN MUỐN DOWNLOAD

Đồng bộ tài khoản