Tham khảo tài liệu ''kỹ thuật lập trình - chương 6'', 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ả
Trang 1Chơng 6Tơ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++ 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ĩ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à 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
public:
void xuat(){
cout << "\n Lop B " ;}
};
class C:public B{
public:
void xuat(){
cout << "\n Lop C " ;}
ơng thức cần gọi
Trang 2Bâ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
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ệ:
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
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ứcxuat() 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ôngọi tới phơng thức A::xuat() vì con trỏ p kiểu A Nhvậy bốn câu lệnh:
#include <conio.h>
#include <stdio.h>
Trang 3}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()
Trang 4Giả 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áodanh 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ọnhoặc in hoặc không) Chơng trình dới đây sử dụnglớp TS (Thí sinh) đáp ứng đợc yêu cầu đặt ra
//CT6-02// Han che phuong thuc tinh// Lop TS
cout << "\nHo ten: " ;fflush(stdin); gets(ht);
cout << "So bao danh: " ;cin >> sobd;
cout << "Tong diem: " ;
Trang 5cout << "\nHo ten: " << ht ;
cout << "\nCo in khong? - C/K" ;
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ứcxem_in, mà sẽ dùng phơng thức xem_in của lớp TS Ch-
ơng trình mới nh sau:
Trang 6void xem_in(){
//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();
Trang 7Khi 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) Nhng lớp TS2 không
this->in() ;
sẽ đợc thực hiện Mặc dù địa chỉ của t[i] (là đối tợngcủa lớp TS2) đợc truyền cho con trỏ this, thế nhngcâ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ơngthứ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() (trongcác lớp TS và TS2) đã không đáp ứng đợc yêu cầu pháttriể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ựctiế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ùngtên, trùng các đối) Để định nghĩa các phơng thứcnày là các phơng thức ảo, ta chỉ cần:
Trang 8+ 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
} ;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” ;};
} ;
Trang 9Chú ý: 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ẽ
virtual void hien_thi() ; } ;
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ạiquy 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êncủ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 ảo
Phơng thức ảo chỉ khác phơng thức tĩnh khi đợcgọ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ỏ cha cho biết rõphơng thức nào (trong số các phơng thức ảo trùng têncủa các lớp có quan hệ thừa kế) sẽ đợc gọi Điều nàyphụ thuộc vào đối tợng cụ thể mà con trỏ đang trỏ tới:
Trang 10Con 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 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
C c ; // c là biến đối tợng kiểu C
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:
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 C
p->hien_thi() ; // Gọi tới C::hien_thi()
p = &d; // p trỏ tới đối tợng 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 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
A a ; // a là biến đối tợng kiểu A
B b ; // b là biến đối tợng kiểu B
C c ; // c là biến đối tợng kiểu C
D 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ù pchứa địa chỉ của các đối tợng a, b, c hay d, thì lờigọ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ơngthứ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ìnhbiên dịch chơng trình
Cũng với lời gọi:
p->hien_thi() ;
nh trên, nhng 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 chaxác định trong giai đoạn dịch chơng trình Lời gọinà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
Trang 11+ liên kết với D::hien_thi() , nếu p chứa địa chỉ
đối tợng lớp D
Nh 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 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
C c ; // c là biến đối tợng kiểu C
D d ; // d là biến đối tợng kiểu D
+ 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:
int n;
public:
A()
Trang 12class 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();
Trang 13(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
triển nâng cấp chơng trình
Ví dụ về các lớp TS và TS2 trong Đ2 đã chỉ ra sự hạnchế của phơng thức tĩnh trong việc sử dụng tínhthừa kế để nâng cấp, phát triển chơng trình Trong
Đ2 cũng đã chỉ ra lớp TS2 cha đáp ứng đợc yêu cầunê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ơngtrình khi đó sẽ nh sau:
//CT6-03B// Sự linh hoạt của phơng thức ảo// Lop TS TS2
cout << "\nHo ten: " ;fflush(stdin); gets(ht);
Trang 14cout << "So bao danh: " ;
cout << "\nHo ten: " << ht ;
cout << "\nCo in khong? - C/K" ;
ch = toupper(getch());
if (ch=='C')
this->in(); // Vì in() là phơng thức ảo nên
//có thể gọi đến TS::in() hoặc TS2::in()
cout << "Dia chi: " ;fflush(stdin); gets(dc);
}void in(){
TS::in();
fprintf(stdprn,"\nDia chi: %s", dc);
}};
void main(){
Dữ liệu thí sinh in ra đã 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() ;
Trang 15Câ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) Nhng lớp TS2 không
cout << "\nHo ten: " << ht ;
cout << "\nCo in khong? - C/K" ;
ch = toupper(getch());
this->in(); // Vì in() là phơng thức ảo nên
//có thể gọi đến TS::in() hoặc
TS2::in()
}
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 Đị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 gọi tới phơng thức
TS2::in() Trong phơng thức TS2::in() có in đị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)
Đ 5 Lớp cơ sở trừu tợng
5.1 Lớp cơ sở trừu tợng
Một lớp cơ sở trừu tợng là một lớp chỉ đợc dùnglàm cơ sở cho các lớp khác Không hề có đối tợng nàocủa một lớp trừu tợng đợc tạo ra cả, bởi vì nó chỉ đợcdùng để định nghĩa một số khái niệm tổng quát,chung cho các lớp khác Một ví dụ về lớp trừu tợng là lớpCON_VAT (con vật), nó sẽ dùng làm cơ sở để xây dựngcác lớp con vật cụ thể nh lớp CON_CHO (con chó),CON_MEO (con mèo), (xem ví dụ bên dới)
Trong C++ , thuật ngữ “Lớp trừu tợng” đặc biệt ápdụng cho các lớp có chứa các phơng thức ảo thuần tuý
Phơng thức ảo thuần tuý là một phơng thức ảo mà nộidung của nó không có gì Cách thức định nghĩa mộtphơng thức ảo thuần tuý nh sau:
virtual void tên_phơng_thức() = 0 ;
Ví dụ:
class A{public:
virtual void nhap() = 0 ;virtual void xuat() = 0 ;void chuong();
} ;Trong ví dụ trên, thì A là lớp cơ sở trừu tợng Các ph-
ơng thức nhap và xuat đợc khai báo là các lớp ảo thuầntuý (bằng cách gán số 0 cho chúng thay cho việc cài
đặt các phơng thức này) Phơng thức chuong() làmột phơng thức bình thờng và sẽ phải có một địnhnghĩa ở đâu đó cho phơng thức này
Không có đối tợng nào của một lớp trừu tợng lại cóthể đợc phát sinh Tuy nhiên các con trỏ và các biến
Trang 16tham 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ừ 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 thuần ảo
mà nó thừa hởng, hoặc bằng các phơng thức ảo
thuần tuý, hoặc bằng những định nghĩa thực sự Ví
dụ:
class B : public A
{
public:
virtual void nhap() = 0 ;
virtual void xuat()
{
// Các câu lệnh
}
} ;
Theo ý nghĩa về hớng đối tợng, ta vẫn có thể có
một lớp trừu tợng mà không nhất thiết phải chứa đựng
những phơng thức thuần tuý ảo
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 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ó 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
5.2 Ví dụ
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:
+ Nhập một con vật mới mua (hoặc chó, hoặc mèo)
vào ô rỗng đầu tiên
+ Xuất (đem bán) một con vật (hoặc chó, hoặcmèo)
+ Thống kê các con vật đang nuôi trong 20 ô
đố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ăngnêu trên của chơng trình Nội dung chơng trình nhsau:
//CT6-04// Lop co so truu tuong// Lop CON_VAT
protected:
char *ten;
public:
Trang 17CON_CHO() : CON_VAT(){
}CON_CHO(char *ten1) : CON_VAT(ten1){
}virtual void xung_ten(){
cout << "\nToi la chu cho: " << ten ;}
Trang 18so_con_vat ;return c;
}elsereturn NULL;
}void DS_CON_VAT::thong_ke(){
if (so_con_vat){
cout << "\n" ;for (int i=0; i<max_so_con_vat; ++i)
if (h[i])h[i]->xung_ten();
}}CON_CHO c1("MUC");
Trang 19Chú ý: Theo quan điểm chung về cách thức sử
dụng, thì 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 cha phải là lớp
cơ sở trừu tợng, vì trong lớp không có các phơng thức
thuần tuý ảo Phơng thức xung_ten:
virtual void xung_ten()
{
}
là phơng thức ảo, đợc định nghĩa đầy đủ , mặc dùthân của nó là rỗng
Do vậy khai báo:
CON_VAT cv(“Con vat chung”);
CON_VAT cv(“Con vat chung”);
sẽ bị C++ bắt lỗi với thông báo:
Cannot create instance of abstruct class ‘CON_VAT’
Đ 6 Sử dụng tơng ứng bội và phơng thức ảo 6.1 Chiến lợc sử dụng tơng ứng bội
Tơng ứng bội cho phép xét các vấn đề khác nhau,các đối tợng khác nhau, các phơng pháp khác nhau,các cách giải quyết khác nhau theo cùng một lợc đồchung
Các bớc áp dụng tơng ứng bội có thể tổng kết lại nhsau:
1 Xây dựng lớp cơ sở trừu tợng bao gồm nhữngthuộc tính 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âydựng các nhóm phơng thức ảo cho các lớp dẫn xuấtsau này Mỗi nhóm phơng thức ảo sẽ thực hiện mộtchức năng nào đó trên các lớp