Tương ứng bội và phương thức ảo Chương 6Tương
ứng bội và phương thức ảoTương
ứng bội và phương thức ảo là công cụ mạnh của C++ 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 chương trình quản lý nhiều đối
tượng khác nhau theo một lược đồ thống nhất.§ 1.
Phương thức tĩnh 1.1. Lời gọi tới
phương thức tĩnhNhư đã 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à 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ừa kế các
phương thức của A
và B. Các
phương thức mà chúng ta 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:class A{public:void xuat(){cout << "\n Lop A " ;}};class B:public A{public:void xuat(){cout << "\n Lop B " ;}};class C:public B{public:void xuat(){cout << "\n Lop C " ;}};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 thức của A
và B. Do đó một đối
tượng của C sẽ có tới 3
phương thức xuat. Hãy theo rõi các câu lệnh sau:C h ; // h là đối
tượng kiểu Ch.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()h.A::xuat() ; // Gọi tới
phương thức h.A::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
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 AA a; // a là đối
tượng kiểu AB b; // b là đối
tượng kiểu BC c; // c là đối
tượng kiểu cChúng ta hãy ghi nhớ mệnh đề sau về con trỏ của các lớp dẫn xuất
và cơ sở:317 318
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.Như vậy cả 3 phép gán sau đều hợp lệ:p = &a ;q = &b ;r = &c ;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, r:p->xuat();q->xuat();r->xuat();và hãy lý giải xem
phương thức nào (trong các
phương thức A::xuat, B::xuat
và C::xuat) được gọi. Câu trả lời như sau:Cả 3 câu lệnh trên đều gọi tới
phương thức A::xuat() , vì các con trỏ p, q
và r đều có kiểu A.Như vậy có thể tóm lược cách
thức gọi các
phương thức tĩnh như sau: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: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.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 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. 1.2. Ví dụ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:void hien(A *p){p->xuat();}Không cần biết tới địa chỉ của đối
tượng nào sẽ truyền cho đối con trỏ p, lời gọi trong hàm luôn luôn gọi tới
phương thức A::xuat() vì con trỏ p kiểu A. Như vậy bốn câu lệnh:hien(&a);hien(&b);hien(&c);hien(&d);trong hàm main (của chương trình dưới đây) đều gọi tới A::xuat().//CT6-01//
Phuong thuc tinh#include <conio.h>#include <stdio.h>#include <iostream.h>#include <ctype.h>class A{private:int n;public:A(){n=0;}A(int n1){n=n1;}void xuat()319 320
{cout << "\nLop A: "<< n;}int getN(){return n;}};class B:public A{public:B():A(){}B(int n1):A(n1){}void xuat(){cout << "\nLop B: "<<getN();}};class C:public A{public:C():A(){}C(int n1):A(n1){}void xuat(){cout << "\nLop C: "<<getN();}};class D:public C{public:D():C(){}D(int n1):C(n1){}void xuat(){cout << "\nLop D: "<<getN();}};void hien(A *p){p->xuat();}void main(){321 322
A a(1);B b(2);C c(3);D d(4);clrscr();hien(&a);hien(&b);hien(&c);hien(&d);getch();}§ 2. Sự hạn chế của
phương thức tĩnhVí dụ sau cho thấy sự hạn chế của
phương thức tĩnh trong việc 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 đưa vào ba thuộc tính: Họ tên, số báo danh
và tổng điểm. Chương 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 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.//CT6-02// Han che
phuong thuc tinh// Lop TS #include <conio.h>#include <stdio.h>#include <iostream.h>#include <ctype.h>class TS{private:char ht[25];int sobd;float td;public:void nhap(){cout << "\nHo ten: " ;fflush(stdin); gets(ht);cout << "So bao danh: " ;cin >> sobd;cout << "Tong diem: " ;cin >> td;}void in(){fprintf(stdprn,"\n\nHo ten: %s", ht);fprintf(stdprn,"\nSo bao danh: %d", sobd);fprintf(stdprn,"\nTong diem: %0.1f", td);}void xem_in(){int ch;cout << "\nHo ten: " << ht ;cout << "\nCo in khong? - C/K" ;ch = toupper(getch());if (ch=='C')this->in();}} ;323 324
void main(){TS t[100];int i, n;cout << "\nSo thi sinh: ";cin >> n;for (i=1; i<=n; ++i)t[i].nhap();for (i=1; i<=n; ++i)t[i].xem_in();getch();}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 TS mà xây dựng lớp mới TS2 dẫn xuất từ lớp TS. Trong lớp TS2 đưa thêm thuộc tính dc (địa chỉ)
và các
phương thức nhap, in. Cụ thể lớp TS2 được định nghĩa như sau:class TS2:public TS{private:char dc[30] ; // Dia chipublic:void nhap(){TS::nhap();cout << "Dia chi: " ;fflush(stdin); gets(dc);}void in(){TS::in();fprintf(stdprn,"\nDia chi: %s", dc);}};Trong lớp TS2 không xây dựng lại
phương thức xem_in, mà sẽ dùng
phương thức xem_in của lớp TS. Chương trình mới như sau://CT6-03// Han che
phuong thuc tinh// Lop TS TS2#include <conio.h>#include <stdio.h>#include <iostream.h>#include <ctype.h>class TS{private:char ht[25];int sobd;float td;public:void nhap(){cout << "\nHo ten: " ;fflush(stdin); gets(ht);cout << "So bao danh: " ;cin >> sobd;cout << "Tong diem: " ;325 326
cin >> td;}void in(){fprintf(stdprn,"\n\nHo ten: %s", ht);fprintf(stdprn,"\nSo bao danh: %d", sobd);fprintf(stdprn,"\nTong diem: %0.1f", td);}void xem_in(){int ch;cout << "\nHo ten: " << ht ;cout << "\nCo in khong? - C/K" ;ch = toupper(getch());if (ch=='C')this->in(); //Goi den TS::in() (Vi this la con tro //kieu TS)}} ;class TS2:public TS{private:char dc[30] ; // Dia chipublic:void nhap(){TS::nhap();cout << "Dia chi: " ;fflush(stdin); gets(dc);}void in(){TS::in();fprintf(stdprn,"\nDia chi: %s", dc);}};void main(){TS2 t[100];int i, n;cout << "\nSo thi sinh: ";cin >> n;for (i=1; i<=n; ++i)t[i].nhap();for (i=1; i<=n; ++i)t[i].xem_in();getch();}Khi
thực hiện chương trình này, chúng ta nhận thấy: Dữ liệu in ra vẫn không có địa chỉ. Điều này có thể giải thích như sau:Xét câu lệnh (thứ 2 từ dưới lên trong hàm main):t[i].xem_in() ;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 thức xem_in, nên
phương thức TS::xem_in() sẽ được gọi tới. Hãy theo rõi
phương thức này:327 328
void xem_in(){int ch;cout << "\nHo ten: " << ht ;cout << "\nCo in khong? - C/K" ;ch = toupper(getch());if(ch=='C')this->in(); //Goi den TS::in() (Vi this la con tro kieu TS)}Các lệnh đầu của
phương thức sẽ in họ tên thí sinh. Nếu chọn có (bấm phím C), thì câu lệnh:this->in() ;sẽ được
thực hiện. Mặc dù địa chỉ của t[i] (là đối
tượng của lớp TS2) đượ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
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 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ội3.1. Cách định nghĩa
phương thức ảoGiả 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
phương thức này là các
phương thức ảo, ta chỉ cần:+ Hoặc thêm từ khoá virtual vào dòng tiêu đề của
phương thức 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.Ví dụ:Cách 1:class A{ .virtual void hien_thi(){cout << “\n Đây là lớp A” ;};} ;class B : public A{ .void hien_thi(){cout << “\n Đây là lớp B” ;};} ;class C : public B{ .void hien_thi(){cout << “\n Đây là lớp C” ;};329 330
} ;class D : public A{ .void hien_thi(){cout << “\n Đây là lớp D” ;};} ;Cách 2:class A{ .virtual void hien_thi(){cout << “\n Đây là lớp A” ;};} ;class B : public A{ .virtual void hien_thi(){cout << “\n Đây là lớp B” ;};} ;class C : public B{ .virtual void hien_thi(){cout << “\n Đây là lớp C” ;};} ;class D : public A{ .virtual void hien_thi(){cout << “\n Đây là lớp D” ;};} ;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).class A{ .virtual void hien_thi() ; } ;virtual void hien_thi() // Sai{cout << “\n Đây là lớp A” ;};Cần sửa lại như sau:class A{ .virtual void hien_thi() ; 331 332
} ;void hien_thi() // Đúng{cout << “\n Đây là lớp A” ;};3.2. Quy tắc gọi
phương thức ảo Để có sự so sánh với
phương thức tĩnh, ta nhắc lại quy tắc gọi
phương thức tĩnh nêu trong §1.3.2.1. Quy tắc gọi
phương thức tĩnhLờ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: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.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 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. 3.2.2. Quy tắc gọi
phương thức ảoPhương
thức ảo chỉ khác
phương thức tĩnh khi được gọi từ một 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 ảo từ một con trỏ chưa cho biết rõ
phương thức nào (trong số 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ỏ đ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.Ví dụ A, B, C
và D là các lớp đã định nghĩa trong 3.1. Ta khai báo một con trỏ kiểu A
và 4 đối tượng:A *p ; // p là con trỏ kiểu AA a ; // a là biến đối
tượng kiểu AB b ; // b là biến đối
tượng kiểu BC c ; // c là biến đối
tượng kiểu CD d ; // d là biến đối
tượng kiểu DXét lời gọi tới các
phương thức ảo hien_thi sau:p = &a; // p trỏ tới đối
tượng a của lớp A p->hien_thi() ; // Gọi tới A::hien_thi() p = &b; // p trỏ tới đối
tượng b của lớp B p->hien_thi() ; // Gọi tới B::hien_thi() p = &c; // p trỏ tới đối
tượng c của lớp Cp->hien_thi() ; // Gọi tới C::hien_thi() p = &d; // p trỏ tới đối
tượng d của lớp Dp->hien_thi() ; // Gọi tới D::hien_thi() 3.3.
Tương ứng bộiChúng ta nhận thấy cùng một câu lệnhp->hien_thi();tương
ứng với nhiều
phương thức khác nhau. Đây chính là
tương ứng bội. Khả năng này rõ ràng cho phép xử lý nhiều đối
tượng khác nhau, nhiều công việc, thậm chí nhiều thuật toán khác nhau theo cùng một cách thức, cùng một lược đồ. Điều này sẽ được minh hoạ trong các mục tiếp theo. 3.4. Liên kết độngCó thể so sánh sự khác nhau giữ
phương thức tĩnh
và phương 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ở lại ví dụ trong 3.2:A *p ; // p là con trỏ kiểu AA a ; // a là biến đối
tượng kiểu AB b ; // b là biến đối
tượng kiểu B333 334
C c ; // c là biến đối
tượng kiểu CD d ; // d là biến đối
tượng kiểu DNế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:p->hien_thi() ; luôn luôn gọi tới
phương thức A::hien_thi() 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 xác định trong quá trình biên dịch chương trình.Cũng với lời gọi:p->hien_thi() ; như trên, nhưng nếu hien_thi() là các
phương thức ảo, thì lời gọi này không liên kết cứng với một
phương thức cụ thể nào.
Phương thức mà nó liên kết (gọi tới) còn chưa xác định trong giai đoạn dịch chương trình. Lời gọi này sẽ:+ liên kết với A::hien_thi() , nếu p chứa địa chỉ đối
tượng lớp A+ liên kết với B::hien_thi() , nếu p chứa địa chỉ đối
tượng lớp B+ liên kết với C::hien_thi() , nếu p chứa địa chỉ đối
tượng lớp C+ liên kết với D::hien_thi() , nếu p chứa địa chỉ đối
tượng lớp DNhư vậy một lời gọi (xuất phát từ con trỏ) tới
phương thức ảo không liên kết với một
phương thức cố định, mà tuỳ thuộc vào nội 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 quá trình chạy chương trình. 3.5. Quy tắc gán địa chỉ đối
tượng cho con trỏ lớp cơ sở+ Như đã nói trong §1, C++ cho phép gán địa chỉ đối
tượng của 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:A *p ; // p là con trỏ kiểu AA a ; // a là biến đối
tượng kiểu AB b ; // b là biến đối
tượng kiểu BC c ; // c là biến đối
tượng kiểu CD d ; // d là biến đối
tượng kiểu Dp = &a; // p
và a cùng lớp A p = &b; // p là con trỏ lớp cơ sở, b là đối
tượng lớp dẫn xuất p = &c; // p là con trỏ lớp cơ sở, c là đối
tượng lớp dẫn xuất p = &d; // p là con trỏ lớp cơ sở, d là đối
tượng lớp dẫn xuất + Tuy nhiên cần chú ý là: Không cho phép gán địa chỉ đối
tượng 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:B *q ;A a ;q = &a;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 B3.6. Ví dụTa sửa chương trình trong §1 bằng cách định nghĩa các
phương thức xuat() là ảo. Khi đó bốn câu lệnh:hien(&a);hien(&b);hien(&c);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
phương thức khác nhau:A::xuat()B::xuat()C::xuat()D::xuat()//CT6-01B335 336
[...]... đị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
và TS2) đã không đáp
ứng được 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
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
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... phát từ một con trỏ kiểu lớp nào, thì
phương
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.
3.2.2. Quy tắc gọi
phương thức ảo
Phương
thức ảo chỉ khác
phương thức tĩnh khi được gọi từ một
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
ảo từ một con trỏ chưa cho biết rõ
phương thức nào (trong số các
phương
thức ảo trùng tên của các lớp có quan hệ thừa... ;
};
3.2. Quy tắc gọi
phương thức ảo
Để có sự so sánh với
phương thức tĩnh, ta nhắc lại quy tắc gọi
phương
thức tĩnh nêu trong
§
1.
3.2.1. 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:
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ẽ... thừa kế của các
phương thức ảo
Cũng giống như
các phương thức thông thường khác,
phương thức
ảo cũng có tính thừa kế. Chẳng hạn trong chương trình trên (mục 3.4)
ta bỏ đi
phương thức xuat() của lớp D, thì câu lệnh:
hien(&d) ;
(câu lệnh gần cuối trong hàm main) sẽ gọi tới C::xuat() ,
phương thức
này được kế thừa trong lớp D (vì D dẫn xuất từ C).
§
4. Sự linh hoạt của
phương thức ảo trong phát... 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
thức xem_in, nên
phương thức TS::xem_in() sẽ được gọi tới. Hãy
theo rõi
phương thức này:
327 328
- Một thành phần dữ liệu là con trỏ a trỏ tới một vùng nhớ chứa
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à a[j].
Phương
thức này được... 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
phương
thức này là các
phương thức ảo, ta chỉ cần:
+ Hoặc thêm từ khố virtual vào dịng tiêu đề của
phương thức bên
trong định nghĩa lớp cơ sở A.
+ Hoặc thêm từ khố 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.
Ví dụ:
Cách 1:
class A
{
virtual... các thuật tốn khác nhau
Có thể sử dụng
tương ứng bội để tổ chức
thực hiện các thuật toán
khác nhau trên cùng một bài toán như sau:
+ 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.
+ 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ể.
+ Sử dụng một mảng con trỏ của lớp cơ sở
và gán cho mỗi phần tử
mảng địa chỉ của... d của lớp D
p->hien_thi() ; // Gọi tới D::hien_thi()
3.3.
Tương ứng bội
Chúng ta nhận thấy cùng một câu lệnh
p->hien_thi();
tương
ứng với nhiều
phương thức khác nhau. Đây chính là
tương ứng
bội. Khả năng này rõ ràng cho phép xử lý nhiều đối
tượng khác nhau,
nhiều cơng việc, thậm chí nhiều thuật tốn khác nhau theo cùng một
cách thức, cùng một lược đồ. Điều này sẽ được minh hoạ trong các
mục...
chương trình
Ví dụ về các lớp TS
và TS2 trong
§
2 đã chỉ ra sự hạn chế của
phương
thức tĩnh trong việc sử dụng tính thừa kế để nâng cấp, phát
triển chương trình. Trong
§
2 cũng đã chỉ ra lớp TS2 chưa đáp
ứng
được yêu cầu nêu ra là in địa chỉ của thí sinh. Giải pháp cho vấn đề
này rất đơn giản: Thay các
phương thức tĩnh in() bằng cách dùng
chúng như các
phương thức ảo. Chương trình khi đó sẽ như...
tượng kiểu C
D d ; // d là biến đối
tượng kiểu D
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:
p->hien_thi() ;
ln ln gọi tới
phương thức A::hien_thi()
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 xác
định trong quá trình biên dịch chương trình.
Cũng . các phương thức in() trong 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ội3 .1. Cách định nghĩa phương thức ảoGiả. Chương 6Tương ứng bội và phương thức ảoTương ứng bội và phương thức ảo là công cụ mạnh của C++ cho phép tổ chức quản