Lập trình C++ - Chương 9: Thừa kế

Trong thực tếhầu hết các lớp có thểkếthừa từcác lớp có trước màkhông cần

định nghĩa lại mới hoàn toàn. Ví dụxemxét một lớp được đặt tên là RecFile

đại diện cho một tập tin gồmnhiều mẫu tin và một lớp khác được đặt tên là

SortedRecFile đại diện cho một tập tin gồmnhiều mẫu tin được sắp xếp. Hai

lớp này có thểcó nhiều điểmchung. Ví dụ, chúng có thểcó các thành viên

hàm giống nhau nhưlà Insert, Delete, và Find, cũng nhưlà thành viên dữliệu

giống nhau. SortedRecFilelà một phiên bản đặc biệt của RecFilevới thuộc tính

các mẫu tin của nó được tổchức theothứtự được thêmvào. Vì thếhầu hết

các hàmthành viên trong cảhai lớp làgiống nhau trong khi một vài hàmmà

phụthuộc vào yếu tốtập tin được sắp xếp thì cóthểkhác nhau. Ví dụ, hàm

Findcó thểlà khác trong lớp SortedRecFilebởi vì nó có thểnhờvào yếu tố

thuận lợi là tập tin được sắp đểthực hiện tìmkiếm nhịphân thay vì tìmtuyến

tính nhưhàm Findcủa lớp RecFile.

pdf20 trang | Chuyên mục: C/C++ | Chia sẻ: dkS00TYs | Lượt xem: 1745 | Lượt tải: 1download
Tóm tắt nội dung Lập trình C++ - Chương 9: Thừa kế, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
}; 
void Menu::Highlight (int part) 
{ 
 OptionList::Highlight(part); 
 Window::Highlight(part); 
} 
9.9. Chuyển kiểu 
Đối với bất kỳ lớp dẫn xuất nào có một sự chuyển kiểu không tường minh từ 
lớp dẫn xuất tới bất kỳ lớp cơ sở chung của nó. Điều này có thể được sử dụng 
để chuyển một đối tượng lớp dẫn xuất thành một đối tượng lớp cơ sở như là 
một đối tượng thích hợp, một tham chiếu, hoặc một con trỏ: 
Menu menu(n, bounds); 
Window win = menu; 
Window &wRef = menu; 
Window *wPtr = &menu; 
Những chuyển đổi như thế là an toàn bởi vì đối tượng lớp dẫn xuất luôn chứa 
đựng tất cả các đối tượng lớp cơ sở của nó. Ví dụ, phép gán đầu tiên làm cho 
thành phần Window của menu được gán tới win. 
 Ngược lại, không có sự chuyển đổi từ lớp cơ sở thành lớp dẫn xuất. Lý 
do một sự chuyển kiểu như thế có khả năng nguy hiểm vì thực tế đối tượng 
lớp dẫn xuất có thể có các dữ liệu thành viên không có mặt trong đối tượng 
lớp cơ sở. Vì thế các thành viên dữ liệu phụ kết thúc bởi các giá trị không thể 
tiên toán. Tất cả chuyển kiểu như thế phải được ép kiểu rõ ràng để xác nhận ý 
định của lập trình viên: 
Menu &mRef = (Menu&) win; // cẩn thận! 
Menu *mPtr = (Menu*) &win; // cẩn thận! 
Một đối tượng lớp cơ sở không thể được gán tới một đối tượng lớp cơ sở trừ 
phi có một hàm xây dựng chuyển kiểu trong lớp dẫn xuất được định nghĩa 
cho mục đích này. Ví dụ, với 
class Menu : public OptionList, public Window { 
public: 
 //... 
 Menu (Window&); 
}; 
Chương 9: Thừa kế 162 
thì câu lệnh gán sau là hợp lệ và có thể sử dụng hàm xây dựng để chuyển đổi 
win thành đối tượng Menu trước khi gán: 
menu = win; // triệu gọi Menu::Menu(Window&) 
9.10.Lớp cơ sở ảo 
Trở lại lớp Menu và giả sử rằng hai lớp cơ sở của nó cũng được dẫn xuất từ 
nhiều lớp khác: 
class OptionList : public Widget, List { /*...*/ }; 
class Window : public Widget, Port { /*...*/ }; 
class Menu : public OptionList, public Window { /*...*/ }; 
 Vì lớp Widget là lớp cơ sở cho cả hai lớp OptionList và Window nên mỗi 
đối tượng menu sẽ có hai đối tượng widget (xem Hình 9.6a). Điều này là 
không mong muốn (bởi vì một menu được xem xét là một widget đơn) và có 
thể dẫn đến mơ hồ. Ví dụ, khi áp dụng hàm thành viên widget tới một đối 
tượng menu, thật không rõ ràng như áp dụng tới một trong hai đối tượng 
widget. Vấn đề này được khắc phục bằng cách làm cho lớp Widget là một lớp 
cơ sở ảo của lớp OptionList và Window. Một lớp cơ sở được làm cho ảo bằng 
cách đặt từ khóa virtual trước tên của nó trong phần đầu lớp dẫn xuất: 
class OptionList : virtual public Widget, List { /*...*/ }; 
class Window : virtual public Widget, Port { /*...*/ }; 
Điều này đảm bảo rằng một đối tượng Menu sẽ chứa đựng vừa đúng một đối 
tượng Widget. Nói cách khác, lớp OptionList và lớp Window sẽ chia sẻ cùng đối 
tượng Widget. 
 Một đối tượng của một lớp mà được dẫn xuất từ một lớp cơ sở ảo không 
chứa đựng trực tiếp đối tượng của lớp cơ sở ảo mà chỉ là một con trỏ tới nó 
(xem Hình 9.6b và 9.6c). Điều này cho phép nhiều hành vi của một lớp ảo 
trong một hệ thống cấp bậc được ghép lại thành một (xem Hình 9.6d). 
 Nếu ở trong một hệ thống cấp bậc lớp một vài thể hiện của lớp cơ sở X 
được khai báo như là ảo và các thể hiện khác như là không ảo thì sau đó đối 
tượng lớp dẫn xuất sẽ chứa đựng một đối tượng X cho mỗi thể hiện không ảo 
của X, và một đối tượng đơn X cho tất cả sự xảy ra ảo của X. 
 Một đối tượng lớp cơ sở ảo không được khởi tạo bởi lớp dẫn xuất trực 
tiếp của nó mà được khởi tạo bởi lớp dẫn xuất xa nhất dưới hệ thống cấp bậc 
lớp. Luật này đảm bảo rằng đối tượng lớp cơ sở ảo được khởi tạo chỉ một lần. 
Ví dụ, trong một đối tượng menu, đối tượng widget được khởi tạo bởi hàm 
Chương 9: Thừa kế 163 
xây dựng Menu (ghi đè lời gọi hàm của hàm xây dựng Widget bởi OptionList 
hoặc Window): 
Menu::Menu (int n, Rect &bounds) : Widget(bounds), 
 OptionList(n), 
 Window(bounds) 
{ //... } 
Không quan tâm vị trí nó xuất hiện trong một hệ thống cấp bậc lớp, một đối 
tượng lớp cơ sở ảo luôn được xây dựng trước các đối tượng không ảo trong 
cùng hệ thống cấp bậc. 
Hình 9.6 Các lớp cơ sở ảo và không ảo 
(a) Menu object
Widget data members
List data members
OptionList data members
Widget data members
Port data members
Window data members
Menu data members
(b) OptionList object with Widget as virtual
Widget data members
List data members
OptionList data members
Widget data members
Port data members
Window data members
(c) Window object with Widget as virtual
Menu data members
(d) Menu object with Widget as virtual
Widget data members
List data members
OptionList data members
Port data members
Window data members
 Nếu trong một hệ thống cấp bậc lớp một cơ sở ảo được khai báo với các 
phạm vi truy xuất đối lập (nghĩa là, bất kỳ sự kết hợp của riêng, được bảo vệ, 
và chung) sau đó khả năng truy xuất lớn nhất sẽ thống trị. Ví dụ, nếu Widget 
được khai báo là một lớp cơ sở ảo riêng của lớp OptionList và là một lớp cơ sở 
ảo chung của lớp Window thì sau đó nó sẽ vẫn còn là một lớp cơ sở ảo chung 
của lớp Menu. 
Chương 9: Thừa kế 164 
9.11.Các toán tử được tái định nghĩa 
Ngoại trừ toán tử gán, một lớp dẫn xuất thừa kế tất cả các toán tử đã tái định 
nghĩa của lớp cơ sở của nó. Toán tử được tái định nghĩa bởi chính lớp dẫn 
xuất che giấu đi việc tái định nghĩa của cùng toán tử bởi các lớp cơ sở (giống 
như là các hàm thành viên của một lớp dẫn xuất che giấu đi các hàm thành 
viên của các lớp cơ sở). 
 Phép gán và khởi tạo memberwise (xem Chương 7) mở rộng tới lớp dẫn 
xuất. Đối với bất kỳ lớp Y dẫn xuất từ X, khởi tạo memberwise được điều 
khiển bởi một hàm xây dựng phát ra tự động (hay do người dùng định nghĩa) 
ở hình thức: 
Y::Y (const Y&); 
Tương tự, phép gán memberwise được điều khiển bởi tái định nghĩa toán tử = 
được phát ra tự động (hay do người dùng định nghĩa): 
Y& Y::operator = (Y&) 
Khởi tạo memberwise (hoặc gán) của đối tượng lớp dẫn xuất liên quan đến 
khởi tạo memberwise (hoặc gán) của các lớp cơ sở của nó cũng như là các 
thành viên đối tượng lớp của nó. 
 Cần sự quan tâm đặc biệt khi một lớp dẫn xuất nhờ vào tái định nghĩa các 
toán tử new và delete cho lớp cơ sở của nó. Ví dụ, trở lại việc tái định nghĩa 
hai toán tử này cho lớp Point trong Chương 7, và giả sử rằng chúng ta muốn 
sử dụng chúng cho một lớp dẫn xuất: 
class Point3D : public Point { 
 public: 
 //... 
 private: 
 int depth; 
}; 
Bởi vì sự cài đặt của Point::operator new giả sử rằng khối được cần sẽ có kích 
thước của đối tượng Point, việc thừa kế của nó bởi lớp Point3D dẫn tới một vấn 
đề: không thể giải thích tại sao dữ liệu thành viên của lớp Point3D (nghĩa là, 
depth) lại cần không gian phụ. 
 Để tránh vấn đề này, tái định nghĩa của toán tử new cố gắng cấp phát vừa 
đúng tổng số lượng lưu trữ được chỉ định bởi tham số kích thước của nó hơn 
là một kích thước giới hạn trước. Tương tự, tái định nghĩa của delete nên chú ý 
vào kích cỡ được chỉ định bởi tham số thứ hai của nó và cố gắng giải phóng 
vừa đúng các byte này. 
Chương 9: Thừa kế 165 
Bài tập cuối chương 9 
9.1 Xem xét lớp Year chia các ngày trong năm thành các ngày làm việc và các 
ngày nghỉ. Bởi vì mỗi ngày có một giá trị nhị phân nên lớp Year dễ dàng được 
dẫn xuất từ BitVec: 
 enum Month { 
Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec 
 }; 
 class Year : public BitVec { 
 public: 
Year (const short year); 
void WorkDay (const short day); // dat ngay nhu ngay lam viec 
void OffDay (const short day); // dat ngay nhu ngay nghi 
Bool Working (const short day); // true neu la ngay lam viec 
short Day (const short day, // chuyen date thanh day 
 const Month month, const short year); 
 protected: 
short year; // nam theo lich 
 }; 
Các ngày được đánh số từ điểm bắt đầu của năm, bắt đầu từ ngày 1 tháng 1 
năm 1. Hoàn tất lớp Year bằng cách thi công các hàm thành viên của nó. 
9.2 Các bảng liệt kê được giới thiệu bởi một khai báo enum là một tập con nhỏ 
của các số nguyên. Trong một vài ứng dụng chúng ta có thể cần xây dựng các 
tập hợp của các bảng liệt kê như thế. Ví dụ, trong một bộ phân tích cú pháp, 
mỗi hàm phân tích có thể được truyền một tập các ký hiệu mà sẽ được bỏ qua 
khi bộ phân tích cú pháp cố gắng phục hồi từ một lỗi hệ thống. Các ký hiệu 
này thông thường được dành riêng những từ của ngôn ngữ: 
enum Reserved {classSym, privateSym, publicSym, protectedSym, 
 friendSym, ifSym, elseSym, switchSym,...}; 
 Với những thứ đã cho có thể có nhiều nhất n phần tử (n là một số nhỏ) tập 
hợp có thể được trình bày có hiệu quả như một vectơ bit của n phần tử. Dẫn 
xuất một lớp đặt tên là EnumSet từ BitVec để làm cho điều này dễ dàng. Lớp 
EnumSet nên tái định nghĩa các toán tử sau: 
• Toán tử + để hợp tập hợp. 
• Toán tử - để hiệu tập hợp. 
• Toán tử * để giao tập hợp. 
• Toán tử % để kiểm tra một phần tử có là thành viên của tập hợp. 
• Các toán tử = để kiểm tra một tập hợp có là một tập con của tập 
khác hay không. 
• Các toán tử >> và << để thêm một phần tử tới tập hợp và xóa một phần tử 
từ tập hợp. 
9.3 Lớp trừu tượng là một lớp mà không bao giờ được sử dụng trực tiếp nhưng 
cung cấp một khung cho các lớp khác được dẫn xuất từ nó. Thông thường, tất 
Chương 9: Thừa kế 166 
cả các hàm thành viên của một lớp trừu tượng là ảo. Bên dưới là một ví dụ 
đơn giản của một lớp trừu tượng: 
class Database { 
 public: 
 virtual void Insert (Key, Data) {} 
 virtual void Delete (Key) {} 
 virtual Data Search (Key) {return 0;} 
}; 
Nó cung cấp một khung cho các lớp giống như cơ sở dữ liệu. Các ví dụ của 
loại lớp có thể được dẫn xuất từ cơ sở dữ liệu gồm: danh sách liên kết, cây 
nhị phân, và B-cây. Trước tiên dẫn xuất lớp B-cây từ lớp Database và sau đó 
dẫn xuất lớp B*-cây từ lớp B-cây: 
class BTree : public Database { /*...*/ }; 
class BStar : public BTree { /*...*/ }; 
Trong bài tập này sử dụng kiểu có sẵn int cho Key và double cho Data. 
Chương 9: Thừa kế 167 

File đính kèm:

  • pdfC++Chuong_09.pdf