Bài giảng Lập trình hướng đối tượng - Chương 9: Tính đa hình
• Con trỏ và Lớp dẫn xuất
• Dẫn nhập các hàm ảo
• Các hàm ảo thuần túy
• Áp dụng đa hình
Tính đa hình (polymorphism) được hổ trợ bằng hai cách khác nhau trong C++ .
Cách 1, đa hình được hổ trợ khi biên dịch chương trình (compiler) thông qua việc quá
tải các hàm và toán tử.
Cách 2, đa hình được hổ trợ ở thời điểm thực thi chương trình (run-time) thông qua
các hàm ảo. Cách này giúp lập trình viên linh động
g có
liên hệ với nhau nhưng khác nhau, và làm mất đi sự phức tạp giả tạo của hệ thống
các hoạt động này.
• Đa hình làm cho mối quan hệ luận lý giữa các hoạt động tương tự nhau được trở
nên rõ ràng hơn, đo đó nó giúp cho lập trình viên dễ dàng hơn trong việc đọc
hiểu và bảo trì chương trình.
Một khi các hoạt động có liên quan với nhau được truy cập bằng duy nhất một giao
diện, giúp sẽ nhớ hơn. Giao diện đồ hoạ trên hệ điều hành Windows hoặc Macintosh
là một điển hình.
2/ Khái niệm về liên kết
Liên kết (binding) liên quan đến OOP và ngôn ngữ C++. Có hai khái niệm :
Chương 9 Tính đa hình 286
• Liên kết sớm (early binding) gắn liền với những biến cố có thể xác định ở thời
điểm biên dịch chương trình. Đặc biệt, liên kết sớm liên quan đến các gọi hàm
được xử lý trong lúc biên dịch bao gồm :
- Các hàm thông thường
- Các hàm được quá tải
- Các hàm thành phần không phải là hàm ảo
- Các hàm friend
Tất cả các thông tin về địa chỉ cần thiết cho việc gọi các hàm trên được xác định rõ
ràng trong lúc biên dịch.
Ưu điểm : gọi các hàm liên kết sớm là kiểu gọi hàm nhanh nhất.
Nhược điểm : thiếu tính linh hoạt.
• Liên kết muộn (late binding) gắn liền với những biến cố xuất hiện trong lúc
thực thi chương trình (run-time).
Khi gọi các hàm liên kết muộn, điạ chỉ của hàm được gọi chỉ biết được khi run-time.
Trong C++, hàm ảo là một đối tượng liên kết muộn. Chỉ khi run-time, hàm ảo được
truy cập bằng con trỏ của lớp cơ sở, chương trình mới xác định kiểu của đối tượng bị
trỏ và biết được phiên bản nào của hàm ảo được thực thi.
Ưu điểm : tính linh hoạt của nó ở thời gian run-time, điều này giúp cho chương trình
gọn gàng vì không có những đoạn chương trình xử lý các biến cố ngẫu nhiên trong
khi thực thi.
Nhược điểm : chậm hơn so với liên kết sớm, do phải qua nhiều giai đoạn trung gian
kèm theo việc gọi thực thi một hàm liên kết muộn.
Mỗi loại liên kết đều có những ưu khuyết điểm riêng của nó, nên phải cân nhắc để
quyết định tình huống thích hợp để sử dụng hai loại liên kết nói trên.
Ví dụ 4.1 Minh họa tư tưởng "một giao diện cho nhiều phương thức" .
Chương 9 Tính đa hình 287
// Demonstrate virtual functons.
#include
#include
#include
class list {
public:
list *head; // pointer to start of list
list *tail; // pointer to end of list
list *next; // pointer to next item
int num; // value to be stored
list() { head = tail = next = NULL; }
virtual void store(int i) = 0;
virtual int retrieve() = 0;
};
// Create a queue type list.
class queue : public list {
public:
void store(int i);
int retrieve();
};
void queue::store(int i)
{
list *item;
item = new queue;
if(!item) {
cout << "Allocation error.\n";
exit(1);
}
item->num = i;
// put on end of list
Chương 9 Tính đa hình 288
if(tail) tail->next = item;
tail = item;
item->next = NULL;
if(!head) head = tail;
}
int queue::retrieve()
{
int i;
list *p;
if(!head) {
cout << "List empty.\n";
return 0;
}
// remove from start of list
i = head->num;
p = head;
head = head->next;
delete p;
return i;
}
// Create a stack type list.
class stack : public list {
public:
void store(int i);
int retrieve();
};
void stack::store(int i)
{
list *item;
item = new stack;
if(!item) {
Chương 9 Tính đa hình 289
cout << "Allocation error.\n";
exit(1);
}
item->num = i;
// put on front of list for stack-like operation
if(head) item->next = head;
head = item;
if(!tail) tail = head;
}
int stack::retrieve()
{
int i;
list *p;
if(!head) {
cout << "List empty.\n";
return 0;
}
// remove from start of list
i = head->num;
p = head;
head = head->next;
delete p;
return i;
}
int main()
{
list *p;
// demonstrate queue
queue q_ob;
p = &q_ob; // point to queue
p->store(1);
Chương 9 Tính đa hình 290
p->store(2);
p->store(3);
cout << "Queue: ";
cout retrieve();
cout retrieve();
cout retrieve();
cout << '\n';
// demonstrate stack
stack s_ob;
p = &s_ob; // point to stack
p->store(1);
p->store(2);
p->store(3);
cout << "Stack: ";
cout retrieve();
cout retrieve();
cout retrieve();
cout << '\n';
return 0;
}
Ví dụ 4.2 Dùng đa hình để xử lý các biến cố ngẫu nhiên.
Sử dụng các khai báo và định nghiã lớp ở ví dụ 4.1 .
int main()
{
list *p;
stack s_ob;
queue q_ob;
Chương 9 Tính đa hình 291
char ch;
int i;
for(i=0; i<10; i++) {
cout << "Stack or Queue? (S/Q): ";
cin >> ch;
ch = tolower(ch);
if(ch=='q')
p = &q_ob;
else
p = &s_ob;
p->store(i);
}
cout << "Enter T to terminate\n";
for(;;) {
cout << "Remove from Stack or Queue? (S/Q): ";
cin >> ch;
ch = tolower(ch);
if(ch=='t') break;
if(ch=='q')
p = &q_ob;
else
p = &s_ob;
cout retrieve() << '\n';
}
cout << '\n';
return 0;
}
• Trong HĐH Windows và OS2, thông qua giao diện người dùng giao tiếp với một
chương trình bằng cách gởi đến chương trình các messages. Những message này
được phát sinh một cách ngẫu nhiên và chương trình của bạn phải đáp ứng các
message này mỗi khi nhận được nó. Do đó, đa hình giúp thực thi chương trình rất
hữu hiệu cho các chương trình được viết để sử dụng trong các HĐH nói trên.
Ví dụ 4.3 Một chương trình đơn giản nhất trên Windows, xuất ra màn hình
Chương 9 Tính đa hình 292
một khung cửa sổ chứa nội dung " Hello, Windows 98 ! "
#include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass)) {
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, // window class name
TEXT ("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
Chương 9 Tính đa hình 293
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL) ; // creation parameters
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM
wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE :
PlaySound (TEXT("hellowin.wav"), NULL, SND_FILENAME |
SND_ASYNC) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT (" Hello, Windows 98 ! "), -1, &rect,
Chương 9 Tính đa hình 294
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
Bài tập IV
1. Thêm vào chương trình ở ví dụ 4.1 một kiểu danh sách liên kết, là loại danh sách
có chức năng xếp các phần tử theo thứ tự tăng dần mỗi khi thêm phần tử mới vào
danh sách. Đặt tên cho lớp này là sorted thừa kế lớp cơ sở list.
Lớp sorted chứa hai hàm chung là
- void store(int i) có chức năng thêm phần tử mới vào danh sách sao cho chúng có
thứ tự tăng dần.
- và int retrieved() có chức năng hiển thị các phần tử trong danh sách.
2. Hãy viết một chương trình có áp dụng tính đa hình.
Bài tập chương 9
Chương 9 Tính đa hình 295
1. Xét đoạn chương trình sau đây. Tìm lỗi và giải thích tại sao ?
class base {
public:
virtual int f(int a) = 0;
// ...
};
class derived : public base {
public:
int f(int a, int b) { return a*b; }
// ...
};
2. Trình bày sự khác nhau giữa hàm ảo và quá tải hàm.
3. Viết chương trình bổ sung vào ví dụ 4.1 chương 9, bằng cách quá tải hai toán tử +
và toán tử -- vào lớp dẫn xuất stack và queue .
Toán tử + thêm một phần tử vào danh sách
stack operator + (int i) ;
queue operator + (int i) ;
và toán tử -- lấy một phần tử ra khỏi danh sách.
int operator -- (int unused) ; // cho cả stack và queue .
4. Hãy sửa đổi một số ví dụ về quá tải hàm trong các chương trước, sao cho có thể
chuyển đổi các hàm được quá tải thành các hàm ảo ?
File đính kèm:
bai_giang_lap_trinh_huong_doi_tuong_tap_1_chuong_9_tinh_da_h.pdf

