191
CHƯƠNG 7
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
++
cho phép tổ chức quản
lý các ñối tượng khác nhau theo cùng một lược ñồ. 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 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 " ;
}
};
A *p, *q, *r; // p, q, r là con trỏ kiểu A
A a; // a là ñối tượng kiểu A
B b; // b là ñối tượng kiểu B
C c; // c là ñối tượng kiểu C
Chú ý: 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 ;
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 là: Cả ba 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:
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. 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. Nếu lời
193
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 bốn 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ả bốn lớp ñều có phương thức xuat.
B():A() { }
B(int n1): A(n1) { }
void xuat()
194
{
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()
{
}
{
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 trên ñây) ñều gọi tới A::xuat().
§
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ử 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:
+ nhap: Nhập dữ liệu thí sinh gồm họ tên, số báo danh, tổng ñiểm
+ xem_in: In họ tên thí sinh ra màn hình, sau ñó lựa chọn hoặc in hoặc không
+ in: In ñầy ñủ dữ liệu thí sinh ra mà hình
Chương trình dưới ñây sử dụng lớp TS (Thí sinh) ñáp ứng ñược yêu cầu ñặt ra.
196
#include <conio.h>
#include <iostream.h>
#include <ctype.h>
class TS
{
char ht[25];
{
197
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 ta 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 chi
public:
void nhap()
{
TS::nhap();
cout << "Dia chi: " ;
fflush(stdin);
gets(dc);
}
cin >> td;
}
void in()
{
cout<<"\nHo ten: "<<ht;
cout<<"\nSo bao danh: "<<sobd;
cout<<"\nTong diem: "<<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
199
{
private:
char dc[30] ; // Dia chi
public:
void nhap()
{
TS::nhap();
cout << "Dia chi: " ;
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 dõi phương thức này:
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. Do ñó 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.
ðể khắc phục ñiều này, chúng ta có thể nghĩ ñến ngay một giải pháp: xây dựng
thêm phương thức xem_in cho lớp TS2. Tuy nhiên ñiều này không chỉ làm cho
chương trình rườm rà hơn mà vẫn không thể ñáp ứng ñược yêu cầu, vì trong TS2 ta
không thể truy nhập ñược các thành phần họ tên, số báo danh, tổng ñiểm.
Trong C
++
có một giải pháp rất hiệu quả ñể xử lý vấn ñề này 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 method).
§
3. PHƯƠNG THỨC ẢO VÀ TƯƠNG ỨNG BỘI
class C : public B
{
...
void hien_thi()
{
cout << “\n ðây là lớp C” ;
}
} ;
class D : public A
{
...
void hien_thi()
{
cout << “\n ðây là lớp D” ;
}
} ;
Cách 2:
202
class A
{
...
virtual void hien_thi()
{
cout << “\n ðây là lớp A” ;
}
} ;
class B : public A
{
} ;
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() ;
} ;
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.
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. 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. 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.
Quy tắc gọi phương thức ảo:
Phương thức ảo khác phương thức tĩnh khi ñược gọi từ một con trỏ. 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
Có 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 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
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() 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ẽ: