Phương pháp trên không thể thực hiện được trong các trường hợp sau: ® Tham số đang chờ đợi lại được dùng để khởi tạo các thành phần khác hoặc được dùng như các tham số truyền của lớp khá
Trang 1Hide(); // Xóa đường tròn cũ
X = NewX; // Thiết lập vị trí mới
H Khoi tao dé hoa
int graphdriver = DETECT;
Trang 3Phương pháp trên không thể thực hiện được trong các
trường hợp sau:
® Tham số đang chờ đợi lại được dùng để khởi tạo các
thành phần khác hoặc được dùng như các tham số
truyền của lớp khác
* Tham sé dang ché đợi được khối tạo được khai báo theo kiểu private trong lớp cơ sở (chỉ có thể trì hoãn việc khởi tạo các thành phần kiểu publie hoặc protected),
8.1.3 Lép hat giéng (Seed class)
Cac lép trong C** duge thiét ké dé thue hién mot mue dich
nào đó và thông thường từ các lớp cơ sở đến các lớp dẫn xuất,
mức độ phức tạp của chúng ngày càng tăng Một trong các
mục đích của việc dẫn xuất một lớp từ một lớp khác là để tránh việc trùng lặp khi viết chương trình Khi có nhiều lớp khác nhau được dẫn xuất từ một lớp khác thì lớp cơ sở này thường rất đơn giản và nó thường chứa các đặc điểm chung của tất cả các lớp dẫn xuất Các lốp mà mục đích được xây dựng chỉ nhằm chia sẻ các đoạn mã cho các lớp dẫn xuất được
307
Trang 4gọi là các lớp hạt giống (seed class) Cần lưu ý là mặc dù các lớp này không cần phải thiết kế như một lớp trừu tượng (xem chương X) nhưng việc sử dụng để khởi tạo các đối tượng cũng
hầu như không đem lại lợi ích gì trong khi xây dựng chương trình Hãy xét đoạn chương trình sau:
BookSelf(int, int, int, int);
int GetColor( ) { return color;}
int GerWidth () { return width;}
int GetHeight( ) { return shelver;}
Desk(int, int, int, int);
int 6etColor( } { return color;}
int GetWidth( } { return width;}
int 6etHeight( ) { return shelves;}
int GetDrawers( ) { return drawers;}
int GetMaterial( ) { return material,}
308
Trang 5Doan chuong trinh trén cé hai lép: BookShelf va Desk Hai
lớp này có rất nhiều đặc điểm chung nhưng vẫn tổn tại các điểm khác nhau giữa chúng Constructor của BookSelƒ có bốn đối số trong khi constructor cia Desk lai cé nam đối số Thêm vào đó thứ tự xuất hiện của các đối số trong hai construefor này cũng có
những điểm khác nhau Để tránh viết lại các điểm được xem là giống nhau giữa hai lớp, ta có thể xây dựng một seed class - lớp Furniture
Trang 6310
int width, height;
public:
Furniture(int, int, int);
int GetColor() { return color;}
int GetWidth() { return width;} int GetHeight() { return shelves;}
BookSelf{int, int, int, int);
int GetShelves(} { return shelves;}
Desk( int, int, int, int, int};
int GetDrawers() { return drawers;} int GetMaterial(} { return material;} }
Trang 7BookSelt::BookSelf( int c, int w, int h, int s) : Furniture(c, w, h)
hiện việc chuyển kiểu từ lớp 8 sang lớp A Tuy vậy điều ngược lại là không được phép
311
Trang 8Trong ví dụ này, khi hàm foo(bp) được gọi, trình biên dịch
sẽ thực hiện việc chuyển bp từ kiểu B sang kiểu A và sau đó truyền tham số cho hàm foo(A* object)
312
Trang 98.1.5 Sử dụng phạm vi trong lớp
"Theo nguyên tắc sử dụng hàm chẳng, trong C' các dữ liệu
và hàm thành phần thuộc các lớp khác nhau có thể được đặt
cùng tên (thậm chí có cùng đối số hoặc kiểu của các đối số trong trường hợp hàm thành phần) Như vậy trong một kiểu dẫn xuất
có thể tồn tại các dữ liệu và hàm thành phản trùng với các dữ liệu và hàm trong lớp cơ sở (trùng tên, trùng đối số, trùng kiểu của các đối số) Trong trường hợp này để có thể gọi chính xác một biến hoặc hàm thuộc lớp cơ sở từ một đối tượng thuộc lớp dẫn xuất C** sử dụng toán tử phạm vi (Scope Resolution)
Một số người lập trình thường nhằm lẫn và không xác định
được giá trị của y khi thực hiện hàm b.foof) Lép B cé 2 hàm
fooQ Một hàm được thừa kế từ lớp Á và một hàm là của chính
313
Trang 10lớp B Trong trường hợp trên z có giá trị là 1 và y có giá trị là 2 Trình biên dịch đã gọi chính xác các hàm thậm chí các hàm đó trùng tên nhau nhờ sử dụng quy tắc về phạm vi Theo quy tắc
này thì nếu tên của các biến hoặc hàm trong lớp cơ sở được khai báo lại trong lớp dẫn xuất thì tên trong lớp dẫn xuất sẽ che khuất tên tương ứng trong lớp cơ sở Điều này cũng giống như khi sử dụng phạm vi của các biến trong từng vùng v.v
Tuy vậy, trong C** cũng tổn tại nhiều điểu khác biệt Ta có
thể bắt buộc trình biên dịch “nhìn” ra ngoài phạm vi hiện thời
và truy nhập đến các tên đã bị che khuất trong phạm vì đó bằng
cách sử dụng các toán tử phạm vi (Scope Resolution Operator) Các biến và hàm được gọi trong bị che khuất có thể được gọi theo
cách sau:
<Tên láp::<Tên biến hoặc hàm>;
Đoạn mã sau sẽ minh họa cho việc sử dụng toán tử phạm vị: class At
int f(}{ return A::foo();}
j/ Sử dụng toán tử phạm vi để gọi fooQ thudc A
Trang 118.1.5 Sử dụng phạm ví trong lớp
Theo nguyên tắc sử dụng hàm chồng, trong C** các dữ liệu
và hàm thành phần thuộc các lớp khác nhau có thể được đặt
cùng tên (thậm chí có cùng đối số hoặc kiểu của các đối số trong
trường hợp hàm thành phần) Như vậy trong một kiểu dẫn xuất
có thể tổn tại các đữ liệu và hàm thành phần trùng với các dữ liệu và hàm trong lớp cơ sở (trùng tên, trùng đối số, trùng kiểu của các đối số) Trong trường hợp này để có thể gọi chính xác một biến hoặc hàm thuộc lớp cơ sở từ một đối tượng thuộc lớp dan xuat C** st dung toan tu pham vi (Scope Resolution)
Trang 12lớp B Trong trường hợp trên z có giá trị là 1 và y có giá trị là 2
"Trình biên địch đã gọi chính xác các hàm thậm chí các hàm đó
trùng tên nhau nhờ sử dụng quy tắc về phạm vị Theo quy tác này thì nếu tên của các biến hoặc hàm trong lớp cơ sở được khai báo lại trong lớp đẫn xuất thì tên trong lớp dẫn xuất sẽ che khuất tên tương ứng trong lớp cơ sở Điểu này cũng giống như khi sử dụng phạm vi của các biến trong từng vùng v.v
Tuy vậy, trong C'* cũng tổn tại nhiều điểu khác biệt Ta có
thể bắt buộc trình biên dịch “nhìn” ra ngoài phạm vi hiện thời
và truy nhập đến các tên đã bị che khuất trong phạm vị đó bằng cách sử dụng các toán tử phạm vi (Scope Resolution Operator)
Các biến và hàm được gọi trong bị che khuất có thể đượe gọi theo
cách sau:
<Tên lớp::<Tên biến hoặc hàm>;
Đoạn mã sau sẽ minh họa cho việc sử dụng toán tử phạm vị:
int f(}{ return A::foo();}
J Sir dung toán tử phạm vi để gọi foo() thuộc A
}
Trong trường hợp này, khi gọi hàm Ö: ƒ#) thi ham foo() của lớp A sẽ được gọi và thực hiện Việc sử dụng toán tử phạm vi không những chỉ được thực hiện bên trong một hàm thuộc một
314
Trang 13lớp mà còn có thể được thực hiện trong thời gian gọi hàm Hãy xét ví dụ dưới đây:
Trong trường hợp chương trình có sử dụng nhiều mức độ
thừa kế khác nhau, toán tử phạm vi vẫn cho phép truy nhập đến
thành phần của bất cứ lớp cơ sở nào Hãy xét cấu trúc thừa kế sau và chương trình minh họa cho cấu trúc này:
Trang 14
int foo(){ return 4;}
int #1(){ rerturn A::foo();}
7 Cho phép sử dụng toán tử phạm vi
int f2(1{ rerturn B:foo();}
j{ Cho phép sử dụng toán tử phạm vi
int £3(){ rerturn C::foo();}
if Cho phép sir dung toán tử phạm ví
int £4(}{ rerturn D::foo(};}
Trang 15Như ta đã biết, một trong những nguyên nhân chính dẫn
đến việc dẫn xuất một lớp từ một lớp cơ số là đo lớp cơ sở có chứa nhiều hàm hoặc đữ liệu mà lớp dân xuất cần sử dụng đến Tuy vậy, để có thể đáp ứng được các yêu câu của lớp dẫn xuất các ham nay còn cần phải phát triển thêm, Việc viết lại toàn bộ các hàm đó trong lớp dẫn xuất sẽ làm phí tổn nhiều thời gian Để tránh vấn để này, C'* cho phép sử dụng lại các đoạn mã của hàm được viết trong lớp cơ sở qua việc mở rộng chúng trong lớp
dẫn xuất Điều này được thực hiện thông qua việc định nghĩa lại các hàm trong lớp cơ sở ở lớp dẫn xuất Hãy xem xét đoạn chương trình dùng để thiết kế một hình tượng (con) cho một nút bấm ấn - thả (puah - button)
class Box {
int left, top;
317
Trang 16int width, hight;
Trang 17const int QUTLINE_HEIGHT = 20;
const int BUTTON_WIDTH = 32;
const int BUTTON_HEIGHT = 16;
Outline=new Box(x,y,OUTLINE_WIDTH OUTLINE_HEIGHT);
int bx = x+ (OUTLINE_WIDTH - BUTTON_WIDTH)/2;
int by = y+(OUTLINE_HEIGHT - BUTTON_ HEIGHT)/2,
Button=new Box(bx,by, BUTTON_WIDTH,BUTTON_HEIGHT)
thước khác nhau Thay vì phải viết lại các công việc này, trong lớp PushButton có định nghĩa lại hàm Display trong lép co sd, hầm này có cùng tên với hàm đã được định nghĩa trong lớp cơ sd Lớp PushButton cố thể được dùng trong ví dụ sau:
319
Trang 18với một hàm khác dùng để hiển thị chuỗi văn bản trong nút,
Điểu này sẽ được giải quyết nếu ta tạo một lớp dẫn xuất từ lớp PushButton va mé réng ham Display cho lớp đó Lớp dẫn xuất
mới được gọi là PwshButtonWHhTile và sẽ sử dụng hàm
PushButton::Display() dé hién thị hình tượng và sau đó sẽ viết
đồng tiêu để lên trên hình tượng đó Có thể biểu diễn sự thừa kế 320
Trang 19của các lớp thông qua cấu trúc cây ở hình 8.3 Trong cấu trúc cây
này, 7ex là một lớp mới không dẫn xuất từ PushButton và dùng
để xuất một xâu ký tự tại điểm có tọa độ (x,y)
Sau đây là đoạn mã của các lớp:
Hinh 8.3
Trang 20Title = new Text(x,y,legend};
Trang 21int gdrv = DETECT, gmode, Err;
Trong ví dụ này, hàm được mở rong cua PushButton là
ĐisplayQ trong lớp mới PushButtonWithTile Ham này không
chỉ vẽ nút bấm (PushButton) mà còn hiển thị dòng mô tả tác
dụng của PushButton nay Trong lép PushButtonWithTile, ham
Display( cha lớp này sử dụng toán tử phạm vi để truy nhập đến
các hàm ¿siay() có cùng tên trong các lớp cơ sở PushButton và Text
8.1.7 Thu hẹp tác động của các hàm thừa kế
Các lớp cơ sở thường chứa các hàm hoặc dữ liệu gần giống
với các hàm và đữ liệu mà lớp dẫn xuất có thể sử dung Goi la
323
Trang 22gần giống vì trong nhiều trường hợp các hàm trong lớp cơ sở có thể chưa đạt được yêu cầu của lớp dẫn xuất Ở trên chúng ta đã xét trường hợp khi cần mở rộng các tác động của các hàm này trong lớp dẫn xuất Trong trường hợp các hàm trong lớp cd SỞ có chứa các tác động không cân thiết trong lớp dẫn xuất, các tác động này cần phải được loại bỏ Trong C**, các lớp dẫn xuất cũng
có thể hạn chế các tác động của lớp cơ sở trên lớp của mình Để
mình họa cho điều này, hãy quay lại ví dụ trên với hình dạng
của PushButton- mới không có hình chữ nhật ở bên trong
Để đạt được muc dich nay, rd rang PushButton: :DisplayO
cân phải được thay đổi để loại bổ tác động không cần thiết - vẽ
thêm một hình chữ nhật ở bên trong Có thể thực hiện điểu này
nếu sử dụng một lớp mới dẫn xuất từ PushBuHon - lớp
SimplePushButton Trong trường hợp này, các lớp được định nghĩa lại như sau:
Trang 23thay đổi nhỏ trong lớp SừnplePushButton, hàm sẽ ngăn không
có các đối tượng của lớp SửnplePushButton biển thị hình chữ
nhật bên trong Điều cần nói ở đây là để đạt được mục đích mới
này, người lập trình không cần thực hiện bất cứ một sự thay đổi
nào trong lớp cơ sở Đây là một điểm rất quan trọng bởi vì các
lớp cơ sở thường chỉ có thể bị thay đổi bởi chính người cung cấp các lớp đó Nguyên nhân ở đây là khi được cung cấp cho người sử
dụng, các lớp này thường đã được biên dịch dưới dạng file Obj 8.2 THỪA KẾ BỘI (MULTIPLE INHERITANCE)
Trong cuội
sống, sự kết hợp giữa cha và mẹ sẽ tạo cho con cái thêm nhiều đặc tính phong phú: đó là khả năng thích nghỉ hơn với điều kiện của cuộc sống và khả năng chọn lọc những đặc tính tốt của cha và mẹ Các lớp dẫn xuất trong C'* không chỉ bị hạn chế bởi một lớp cơ sở duy nhất mà còn có thể thừa kế nhiều
325
Trang 24lớp cø sở khác nhau Khi đó lớp dẫn xuất sẽ thừa kế tất cả các đặc tính mà mỗi lớp cơ sở có được Sự thừa kế này được gọi là
thừa kế bội và mặc dù có thể đẫn đến sự phức tạp trong ngôn ngữ cũng như đối với trình biên dịch đặc tính này cũng mang lại
rất nhiều điều thuận lợi trong khi thiết kế chương trình
Hãy xét một lớp RoundTable, lớp này không chỉ có các đặc
tính của lớp Tøöie mà còn phải có các đặc tính của lép Circle chứa các đặc tính của hình dạng bao quanh Sự thừa kế này có thể được mô tả qua cấu trúc cây sau:
Trang 25RoundTable(float h, float r, int c);
int Color( }{ return Color;}
b
RoundTable::RoundTable(float h, float r, int c):
Cirle(r),Table(h) {
prinf(®n Các đặc tinh cia bang 18 :”);
printfC^n Chiều rong =f”, table.Height());
printf(‘An Diện tich =%f”, table Area());
printf(‘An Mau =%d”, table.Color());
}
Trong vi du trén, lép RoundTable a4 trd thanh rat don gian
đo thừa hưởng rất nhiều đặc tính từ các lớp cơ sở Khi khai báo một lớp dẫn xuất nào đó mang tính thừa kế bội, cần khai báo các lớp cơ sở của nó Sau khi được khai báo, các lớp cd sở này có thể được truy nhập trực tiếp trong lớp đẫn xuất của chúng Sự truy
327
Trang 26nhập này hoàn toàn tương tự giống như trong trường hợp thừa
kế đơn
Ham mưinQ) trong vi du trên gọi 8 hàm thành phần
RoundTabl eightQ, RoundToble::Ared() và RoundTubie:Color
mà không cần phân biệt chúng có phải là hàm thừa kế hay
không Phương pháp truy nhập này cũng được sử dụng trong
trường hợp thừa kế đơn
8.2.1 Thực hiện Constructor của các lớp cơ sở
Giống như trường hợp thừa kế đơn, các consirucfor của các
lớp cơ sở trong trường hợp thừa kế bội cần phải được gọi trước
construcior của lớp dẫn xuất Thứ tự gọi các consfruefor của các
lớp này sẽ phụ thuộc vào thứ tự khai báo của chúng trong lớp dẫn xuất Hãy xét lại lớp RoundTable:
class RoundTable:public Table, public Circle {
int Color;
public;
RoundTable(float hy floar int 7
int Color() { return Color;}
}
Các lớp cơ sở được khai báo trong lớp dẫn xuất có thứ tự lần
lugt 1a Table, Circle Nhu vay khi thé hiện một đối tượng thuộc lớp RoundTable, các construetor sẽ lần lượt được gọi theo thứ tự