c being onstruted being destroyed
9.5. Lớp cơ sở riêng, chung, và được bảo vệ
Một lớp cơ sở có thể được chỉ định là riêng, chung, hay được bảo vệ. Nếu không được chỉđịnh như thế, lớp cơ sởđược giả sử là riêng:
class A {
private: int x; void Fx (void);
public: int y; void Fy (void); protected: int z; void Fz (void); };
class B : A {}; // A la lop co so rieng cua B class C : private A {}; // A la lop co so rieng cua C class D : public A {}; // A la lop co so chung cua D class E : protected A {}; // A la lop co so duoc bao ve cua E
Cư xử của những lớp này là như sau (xem Bảng 9.1 cho một tổng kết):
• Tất cả các thành viên của một lớp cơ sở riêng trở thành các thành viên
riêng của lớp dẫn xuất. Vì thế tất cả x, Fx, y, Fy, z, và Fz trở thành các thành viên riêng của B và C.
• Các thành viên của lớp cơ sở chung giữ các đặc điểm truy xuất của chúng trong lớp dẫn xuất. Vì thế, x và Fx trở thành các thành viên riêng D, y và Fy trở thành các thành viên chung của D, và z và Fz trở thành các thành viên được bảo vệ của D.
• Các thành viên riêng của lớp cơ sởđược bảo vệ trở thành các thành viên
riêng của lớp dẫn xuất. Nhưng ngược lại, các thành viên chung và được bảo vệ của lớp cơ sởđược bảo vệ trở thành các thành viên được bảo vệ
của lớp dẫn xuất. Vì thế, x và Fx trở thành các thành viên riêng của E, và y, Fy, z, và Fz trở thành các thành viên được bảo vệ của E.
Bảng 9.1 Các qui luật thừa kế truy xuất lớp cơ sở.
Lớp cơ sở Dẫn xuất riêng Dẫn xuất chung Dẫn xuất được bảo vệ
Private Member private private private
Public Member private public protected
Protected Member private protected protected
Chúng ta cũng có thể miễn cho một thành viên riêng lẻ của lớp cơ sở từ
những chuyển đổi truy xuất được đặc tả bởi một lớp dẫn sao cho nó vẫn giữ
lại những đặc điểm truy xuất gốc của nó. Để làm điều này, các thành viên
được miễn được đặt tên đầy đủ trong lớp dẫn xuất với đặc điểm truy xuất gốc của nó. Ví dụ:
class C : private A { //...
public: A::Fy; // lam cho Fy la mot thanh vien chung cua C protected: A::z; // lam cho z la mot thanh vien duoc bao ve
// cua C };
9.6. Hàm ảo
Xem xét sự thay đổi khác của lớp ContactDir được gọi là SortedDir, mà đảm bảo rằng các đối tác mới được xen vào phần còn lại của danh sách đã được sắp xếp. Thuận lợi rõ ràng của điều này là tốc độ tìm kiếm có thể được cải thiện bằng cách sử dụng giải thuật tìm kiếm nhị phân thay vì tìm kiếm tuyến tính.
Việc tìm kiếm trong thực tế được thực hiện bởi hàm thành viên Lookup. Vì thế chúng ta cần định nghĩa lại hàm này trong lớp SortedDir sao cho nó sử
dụng giải thuật tìm kiếm nhị phân. Tuy nhiên, tất cả các hàm thành viên khác tham khảo tới ContactDir::Lookup. Chúng ta cũng có thể định nghĩa các hàm này sao cho chúng tham khảo tới SortedDir::Lookup. Nếu chúng ta theo tiếp cận này, giá trị của thừa kế trở nên đáng ngờ hơn bởi vì thực tế chúng ta có thể
Thực sự cái mà chúng ta muốn làm là tìm cách để biểu diễn điều này: hàm Lookup nên được liên kết tới kiểu của đối tượng mà triệu gọi nó. Nếu đối tượng thuộc kiểu SortedDir sau đó triệu gọi Lookup (từ bất kỳ chỗ nào, thậm
chí từ bên trong các hàm thành viên của ContactDir) có nghĩa là
SortedDir::Lookup. Tương tự, nếu đối tượng thuộc kiểu ContactDir sau đó gọi Lookup (từ bất kỳ chỗ nào) có nghĩa là ContactDir::Lookup.
Điều này có thể được thực thi thông qua liên kết động (dynamic binding) của hàm Lookup: sự quyết định chọn phiên bản nào của hàm Lookup
để gọi được tạo ra ở thời gian chạy phụ thuộc vào kiểu của đối tượng.
Trong C++, liên kết động được hỗ trợ thông qua các hàm thành viên ảo. Một hàm thành viên được khai báo như là ảo bằng cách chèn thêm từ khóa virtual trước nguyên mẫu (prototype) của nó trong lớp cơ sở. Bất kỳ hàm thành viên nào, kể cả hàm xây dựng và hàm hủy, có thể được khai báo như ảo. Hàm Lookup nên được khai báo nhưảo trong lớp ContactDir:
class ContactDir { //... public:
virtual int Lookup (const char *name); //...
};
Chỉ các hàm thành viên không tĩnh có thểđược khai báo như là ảo. Một hàm thành viên ảo được định nghĩa lại trong một lớp dẫn xuất phải có chính xác cùng tham số và kiểu trả về như một hàm thành viên trong lớp cơ sở. Các hàm ảo có thểđược tái định nghĩa giống như các thành viên khác.
Danh sách 9.3 trình bày định nghĩa của lớp SortedDir như lớp dẫn xuất của lớp ContactDir. Danh sách 9.3 1 2 3 4 5 6
class SortedDir : public ContactDir { public:
SortedDir (const int max) : ContactDir(max) {} public:
virtual int Lookup (const char *name); };
Chú giải
3 Hàm xây dựng đơn giản chỉ gọi hàm xây dựng lớp cơ sở.
5 Hàm Lookup được khai báo lại như là ảo để cho phép bất kỳ lớp nào được dẫn xuất từ lớp SortedDir định nghĩa lại nó.
Định nghĩa mới của hàm Lookup như sau:
int SortedDir::Lookup (const char *name) {
int bot = 0; int top = dirSize - 1;
int pos = 0; int mid, cmp;
while (bot <= top) { mid = (bot + top) / 2;
if ((cmp = strcmp(name, contacts[mid]->Name())) == 0) return mid;
else if (cmp < 0)
pos = top = mid - 1; // gioi han tim tren nua thap hon else
pos = bot = mid + 1; // gioi han tim tren nua cao hon }
return pos < 0 ? 0 : pos; }
Đoạn mã sau minh họa rằng hàm SortedDir::Lookup được gọi bởi hàm ContactDir::Insert khi được triệu gọi thông qua đối tượng SortedDir:
SortedDir dir(10); dir.Insert(Contact("Mary", "11 South Rd", "282 1324")); dir.Insert(Contact("Peter", "9 Port Rd", "678 9862")); dir.Insert(Contact("Jane", "321 Yara Ln", "982 6252")); dir.Insert(Contact("Jack", "42 Wayne St", "663 2989")); dir.Insert(Contact("Fred", "2 High St", "458 2324")); cout << dir; Nó sẽ cho ra kết quả sau: (Fred , 2 High St , 458 2324) (Jack , 42 Wayne St , 663 2989) (Jane , 321 Yara Ln , 982 6252) (Mary , 11 South Rd , 282 1324) (Peter , 9 Port Rd , 678 9862) 9.7. Đa thừa kế
Các lớp dẫn xuất mà chúng ta đã bắt gặp đến thời điểm này trong chương này chỉ là biểu diễn đơn thừa kế, bởi vì mỗi lớp thừa kế các thuộc tính của nó từ
một lớp cơ sởđơn. Một tiếp cận có thể khác, một lớp dẫn xuất có thể có nhiều lớp cơ sở. Điều này được biết đến như là đa thừa kế (multiple inheritance). Ví dụ, chúng ta phải định nghĩa hai lớp tương ứng để biểu diễn các danh sách của các tùy chọn và các cửa sổđiểm ảnh: class OptionList { public: OptionList (int n); ~OptionList (void); //... }; class Window { public:
Window (Rect &bounds); ~Window (void);
Một menu là một danh sách của các tùy chọn được hiển thịở bên trong cửa sổ
của nó. Vì thế nó có thểđịnh nghĩa Menu bằng cách dẫn xuất từ lớp OptionList và lớp Window:
class Menu : public OptionList, public Window { public:
Menu (int n, Rect &bounds); ~Menu (void);
//... }; };
Với đa thừa kế, một lớp dẫn xuất thừa kế tất cả các thành viên của các lớp cơ sở của nó. Như trước, mỗi thành viên của lớp cơ sở có thể là riêng, chung, hay là được bảo vệ. Áp dụng cùng các nguyên lý truy xuất thành viên lớp cơ sở. Hình 9.4 minh họa thứ bậc lớp cho Menu..
Hình 9.4 Thứ bậc lớp cho Menu
OptionLis t Window
Menu
Vì các lớp cơ sở của lớp Menu có các hàm xây dựng yêu cầu các đối số
nên hàm xây dựng cho lớp dẫn xuất nên triệu gọi những hàm xây dựng trong danh sách khởi tạo thành viên của nó:
Menu::Menu (int n, Rect &bounds) : OptionList(n), Window(bounds) {
//... } }
Thứ tự mà các hàm xây dựng được triệu gọi cùng với thứ tự mà chúng được
đặc tả trong phần đầu của lớp dẫn xuất (không theo thứ tự mà chúng xuất hiện trong danh sách khởi tạo thành viên của các hàm xây dựng lớp dẫn xuất). Ví dụ, với Menu hàm xây dựng cho lớp OptionList được triệu gọi trước khi hàm xây dựng cho lớp Window, thậm chí nếu chúng ta chuyển đổi thứ tự trong hàm xây dựng:
Menu::Menu (int n, Rect &bounds) : Window(bounds), OptionList(n) {
//... } }
Các hàm hủy được ứng dụng theo thứ tự ngược lại: ~Menu, kếđó là ~Window, và cuối cùng là ~OptionList.
Sự cài đặt rõ ràng của đối tượng lớp dẫn xuất là chứa đựng một đối tượng từ mỗi đối tượng của các lớp cơ sở của nó. Hình 9.5 minh họa mối quan hệ
giữa một đối tượng lớp Menu và các đối tượng lớp cơ sở.
Hình 9.5 Các đối tượng lớp dẫn xuất và cơ sở.
OptionList object OptionList data members Window object Window data members Menu object OptionList data members Window data members Menu data members
Thông thường, một lớp dẫn xuất có thể có số lượng các lớp cơ sở bất kỳ, tất cả chúng phải phân biệt:
class X : A, B, A { // không hợp qui tắc: xuất hiện hai lần //...
};