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