Các hàm bạn bè (từ khoá friend)
Trong bài trước chúng ta đã được biết rằng có ba mức bảo vệ khác nhau đối với các thành viên trong một lớp:
public,
protected và
private. Đối với các thành viên
protected và
private, chúng không thể được truy xuất ở bên ngoài lớp mà chúng được khai báo. Tuy nhiên cái gì cũng có ngoại lệ, bằng cách sử dụng từ khoá
friend trong một lớp chúng ta có thể cho phép một hàm bên ngoài truy xuất vào các thành viên
protected và
private trong một lớp.
Để có thể cho phép một hàm bên ngoài truy xuất vào các thành viên
private và
protected của một lớp chúng ta phải khai báo mẫu hàm đó với từ khoá
friend bên trong phần khai báo của lớp. Trong ví dụ sau chúng ta khai báo hàm bạn bè
duplicate:
// friend functions #include <iostream.h> class CRectangle { int width, height; public: void set_values (int, int); int area (void) {return (width * height);} friend CRectangle duplicate (CRectangle); }; void CRectangle::set_values (int a, int b) { width = a; height = b; } CRectangle duplicate (CRectangle rectparam) { CRectangle rectres; rectres.width = rectparam.width*2; rectres.height = rectparam.height*2; return (rectres); } int main () { CRectangle rect, rectb; rect.set_values (2,3); rectb = duplicate (rect); cout << rectb.area(); }
|
24 |
Ở bên trong hàm
duplicate, chúng ta có thể truy xuất vào các thành viên
width và
height của các đối tượng khác nhau thuộc lớp
CRectangle. Hãy chú ý rằng
duplicate() không phải là thành viên của lớp
CRectangle.
Nói chung việc sử dụng các hàm bạn bè không nằm trong phương thức lập trình hướng đối tượng, vì vậy tốt hơn là hãy sử dụng các thành viên của lớp bất cứ khi nào có thể. Như ở trong ví dụ trước, chúng ta hoàn toàn có thể tích hợp
duplicate() vào bên trong lớp.
Các lớp bạn bè (friend)
Ngoài việc có thể khai báo các hàm bạn bè, chúng ta cũng có thể định nghĩa một lớp là bạn của một lớp khác. Việc này sẽ cho phép lớp thứ hai có thể truy xuất vào các thành viên
protected and
private của lớp thứ nhất:
// friend class #include <iostream.h> class CSquare; class CRectangle { int width, height; public: int area (void) {return (width * height);} void convert (CSquare a); }; class CSquare { private: int side; public: void set_side (int a) {side=a;} friend class CRectangle; }; void CRectangle::convert (CSquare a) { width = a.side; height = a.side; } int main () { CSquare sqr; CRectangle rect; sqr.set_side(4); rect.convert(sqr); cout << rect.area(); return 0; }
|
16 |
Trong ví dụ này chúng ta đã khai báo
CRectangle là bạn của
CSquare nên
CRectangle có thể truy xuất vào các thành viên
protected and
private của
CSquare, cụ thể hơn là
CSquare::side, biến định nghĩa kích thước của hình vuông.
Bạn có thể thấy một điều mới lạ trong chương trình, đó là phần khai báo mẫu rỗng của lớp
CSquare, điều này là cần thiết vì bên trong phần khai báo của
CRectangle chúng ta tham chiếu đến
CSquare (như là một tham số trong
convert()). Phần định nghĩa đầy đủ của
CSquare được viết ở sau.
Chú ý rằng tình bạn giữa hai lớp có thể không như nhau nếu chúng ta không chỉ định. Trong ví dụ này
CRectangle được coi là bạn của
CSquare nhưng đối với
CRectangle thì không. Bởi vậy
CRectangle có thể truy xuất vào các thành viên
protected và
private của
CSquare nhưng điều ngược lại là không đúng. Tuy nhiên chẳng có gì cấm đoán chúng ta khai báo
CSquare là bạn của
CRectangle.
Sự thừa kế giữa các lớp
Một trong những tính năng quan trọng của lớp là sự thừa kế. Nó cho phép chúng ta tạo một đối tượng xuất pháp từ một đối tượng khác. Ví dụ, giả sử chúng ta muốn khai báo một loạt các lớp mô tả các đa giác như là
CRectangle hay
CTriangle. Cả hai đều có những đặc tính chung, ví dụ như là chiều cao và đáy.
Điều này có thể được biểu diễn bằng lớp
CPolygon mà từ đó chúng ta có thể thừa kế hai lớp, đó là
CRectangle và
CTriangle.
Lớp
CPolygon sẽ chứa các thành viên chung đối với mọi đa giác. Trong trường hợp của chúng ta: chiều rộng và chiều cao.
Các lớp xuất phát từ các lớp khác được thừa hưởng tất cả các thành viên nhìn thấy được của lớp. Điều này có nghĩa là một lớp cơ sở có thành viên
A và chúng ta tạo thêm một lớp xuất phát từ nó với một thành viên mới là
B, lớp được thừa kế sẽ có cả
A và
B.
Để có thể thừa kế một lớp từ một lớp khác, chúng ta sử dụng toán tử
: (dấu hai chấm ) trong phần khai báo của lớp con:
class derived_class_name: public base_class_name;
trong đó
derived_class_name là tên của lớp con (lớp được thừa kế) và
base_class_name là tên của lớp cơ sở.
public có thể được thay thế bởi
protected hoặc
private, nó xác định quyền truy xuất đối với các thành viên được thừa kế như chúng ta sẽ thấy ở ví dụ này:
// derived classes #include <iostream.h> class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b;} }; class CRectangle: public CPolygon { public: int area (void) { return (width * height); } }; class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } }; int main () { CRectangle rect; CTriangle trgl; rect.set_values (4,5); trgl.set_values (4,5); cout << rect.area() << endl; cout << trgl.area() << endl; return 0; }
|
20 10 |
Như bạn có thể thấy, các đối tượng của lớp
CRectangle và
CTriangle chứa tất cả các thành viên của
CPolygon, đó là
width,
height và
set_values().
Từ khoá
protected tương tự với
private, sự khác biệt duy nhất chỉ xảy ra khi thừa kế các lớp. Khi chúng ta thừa kế một lớp, các thành viên
protected của lớp cơ sở có thể được dùng bởi các thành viên khác của lớp được thừa kế còn các thành viên
private thì không. Vì chúng ta muốn rằng
width và
height có thể được tính toán bởi các thành viên của các lớp được thừa kế
CRectangle và
CTriangle chứ không chỉ bởi các thành viên của
CPolygon, chúng ta đã sử dụng từ khoá
protected thay vì
private.
Chúng ta có thể tổng kết lại các kiểu truy xuất khác nhau tuỳ theo ai truy xuất chúng:
Truy xuất |
public |
protected |
private |
Các thành viên trong cùng lớp |
có |
có |
có |
Các thành viên của các lớp thừa kế |
có |
có |
không |
Không phải là thành viên |
có |
không |
không |
trong đó "không phải là thành viên" đại diện cho bất kì sự tham chiếu nào từ bên ngoài lớp, ví dụ như là từ
main(), từ một lớp khác hay từ bất kì hàm nào.
Trong ví dụ của chúng ta, các thành viên được thừa kế bởi
CRectangle và
CTriangle cũng tuân theo các quyền truy xuất như đối với lớp cơ sở
CPolygon:
CPolygon::width // protected access
CRectangle::width // protected access
CPolygon::set_values() // public access
CRectangle::set_values() // public access
Có điều này vì chúng ta đã thừa kế một lớp từ một lớp khác với quyền truy xuất
public, hãy nhớ:
class CRectangle: public CPolygon;
từ khoá
public đại diện cho mức độ bảo về
tối thiểu mà các thành viên được thừa kế của lớp cơ sở (
CPolygon) phải có được trong lớp mới (
CRectangle). Mức độ này đối với các thành viên được thừa kế có thể được thay đổi nếu thay vì dùng
public chúng ta sử dụng
protected hay
private, ví dụ, giả sử rằng
daughter là một lớp được thừa kế từ
mother, chúng định nghĩa như thế này:
class daughter: protected mother;
điều này sẽ thiết lập
protected là mức độ truy xuất tối thiểu cho các thành viên của
daughter được thừa kế từ lớp cơ sở. Có nghĩa là tất cả các thành viên
public trong
mother sẽ trở thành
protected trong
daughter. Tất nhiên, điều này không cản trở
daughter có thể có các thành viên
public của riêng nó. Mức độ tối thiểu này chỉ áp dụng cho các thành viên được thừa kế từ
mother.
Nếu không có mức truy xuất nào được chỉ định,
private được dùng với các lớp được tạo ra với từ khoá
class còn
public được dùng với các cấu trúc.
Những gì được thừa kế từ lớp cơ sở?
Về nguyên tắc tất cả các thành viên của lớp đều được thừa kế trừ:
- Constructor và destructor
- Thành viên operator=()
- Bạn bè
Mặc dù constructor và destructor của lớp cơ sở không được thừa kế, constructor mặc định (constructor không có tham số) và destructor của lớp cơ sở luôn luôn được gọi khi một đối tượng của lớp được thừa kế được tạo lập hay phá huỷ.
Nếu lớp cơ sở không có constructor mặc định hay bạn muốn một constructor đã quá tải được gọi khi một đối tượng mới của lớp được thừa kế được tạo lập, bạn có thể chỉ định nó ở mỗi định nghĩa của constructor trong lớp được thừa kế:
derived_class_name (parameters) : base_class_name (parameters) {}
Ví dụ:
// constructors and derivated classes #include <iostream.h> class mother { public: mother () { cout << "mother: no parameters
"; } mother (int a) { cout << "mother: int parameter
"; } }; class daughter : public mother { public: daughter (int a) { cout << "daughter: int parameter
"; } }; class son : public mother { public: son (int a) : mother (a) { cout << "son: int parameter
"; } }; int main () { daughter cynthia (1); son daniel(1); return 0; }
|
mother: no parameters daughter: int parameter mother: int parameter son: int parameter
|
Hãy quan sát sự khác biệt giữa việc constructor của
mother được gọi khi khi một đối tượng
daughter mới được tạo lập và khi một đối tượng
son được tạo lập. Sở dĩ có sự khác biệt này là do phần khai báo constructor của
daughter và
son:
daughter (int a) // không có gì được chỉ định: gọi constructor mặc định
son (int a) : mother (a) // constructor được chỉ định: gọi cái này
Đa thừa kế
Trong C++ chúng ta có thể tạo lập một lớp thừa kế các trường và các phương thức từ nhiều hơn một lớp. Điều này có thể thực hiện bằng cách tách các lớp cơ sở khác nhau bằng dấu phẩy trong phần khai báo của lớp được thừa kế. Ví dụ, nếu chúng ta có một lớp dùng để in ra màn hình (
COutput) và chúng ta muốn các lớp của chúng ta
CRectangle và
CTriangle cũng thừa kế các thành viên của nó cùng với các thành viên của
CPolygon, chúng ta có thể viết:
class CRectangle: public CPolygon, public COutput {
class CTriangle: public CPolygon, public COutput {
Đây là ví dụ đầy đủ:
// đa thừa kế #include <iostream.h> class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b;} }; class COutput { public: void output (int i); }; void COutput::output (int i) { cout << i << endl; } class CRectangle: public CPolygon, public COutput { public: int area (void) { return (width * height); } }; class CTriangle: public CPolygon, public COutput { public: int area (void) { return (width * height / 2); } }; int main () { CRectangle rect; CTriangle trgl; rect.set_values (4,5); trgl.set_values (4,5); rect.output (rect.area()); trgl.output (trgl.area()); return 0; }
|
20 10 |