Một đối tượng của một lớp dẫn xuất kế thừa public cũng có thể được xử lý như một đối tượng của lớp cơ sở của nó tương ứng. Nhưng ngược lại không đúng: một đối tượng lớp cơ sở cũng không tự động là một
đối tượng lớp dẫn xuất.
Tuy nhiên, có thể sử dụng ép kiểu để chuyển đổi một con trỏ lớp cơ sở thành một con trỏ lớp dẫn xuất. Ví dụ 5.1: Chương trình sau sẽ được chia thành nhiều file (gồm các file .H và .CPP) và tạo một project có tên là CT5_1.PRJ gồm các file .cpp File POINT.H: 1: //POINT.H 2: //Định nghĩa lớp Point 3: #ifndef POINT_H 4: #define POINT_H 5: 6: class Point 7: { 8: protected: 9: float X,Y; 10: public: 11: Point(float A= 0, float B= 0); 12: void SetPoint(float A, float B); 13: float GetX() const
14: {
15: return X; 16: }
17: float GetY() const 18: {
19: return Y; 20: }
21: friend ostream & operator <<(ostream &Output, const Point &P); 22: };
23:
24: #endif
File POINT.CPP
1: //POINT.CPP
2: //Định nghĩa các hàm thành viên của lớp Point 3: #include <iostream.h>
110 5: 6: Point::Point(float A, float B) 7: { 8: SetPoint(A, B); 9: } 10:
11: void Point::SetPoint(float A, float B) 12: {
13: X = A; 14: Y = B; 15: } 16:
17: ostream & operator <<(ostream &Output, const Point &P) 18: { 19: Output << '[' << P.X << ", " << P.Y << ']'; 20: return Output; 21: } File CIRCLE.H 1: //CIRCLE.H 2: //Định nghĩa lớp Circle 3: #ifndef CIRCLE_H 4: #define CIRCLE_H 5: 6: #include "point.h"
7: class Circle : public Point 8: {
9: protected: 10: float Radius; 11: public:
12: Circle(float R = 0.0, float A = 0, float B = 0); 13: void SetRadius(float R);
14: float GetRadius() const; 15: float Area() const;
16: friend ostream & operator <<(ostream &Output, const Circle &C); 17: };
18:
19: #endif
File CIRCLE.CPP
1: //CIRCLE.CPP
2: //Định nghĩa các hàm thành viên của lớp Circle 3: #include <iostream.h>
4: #include <iomanip.h> 5: #include "circle.h" 6:
7: Circle::Circle(float R, float A, float B): Point(A, B) 8: { 9: Radius = R; 10: } 11: 12: void Circle::SetRadius(float R) 13: { 14: Radius = R; 15: } 16:
17: float Circle::GetRadius() const 18: {
Giáo trình mơn Lập trình hướng đối tượng Trang 111 19: return Radius;
20: } 21:
22: float Circle::Area() const 23: {
24: return 3.14159 * Radius * Radius; 25: }
26:
27: //Xuất một Circle theo dạng: Center = [x, y]; Radius = #.## 28: ostream & operator <<(ostream &Output, const Circle &C) 29: {
30: Output << "Center = [" << C.X << ", " << C.Y 31: << "]; Radius = " << setiosflags(ios::showpoint) 32: << setprecision(2) << C.Radius; 33: return Output; 34: } File CT5_1.CPP: 1: //CT5_1.CPP
2: //Chương trình 5.1: Ép các con trỏ lớp cơ sở tới các con trỏ lớp dẫn xuất 3: #include <iostream.h> 4: #include <iomanip.h> 5: #include "point.h" 6: #include "circle.h" 7: 8: int main() 9: { 10: Point *PointPtr, P(3.5, 5.3); 11: Circle *CirclePtr, C(2.7, 1.2, 8.9);
12: cout << "Point P: "<<P<<endl<<"Circle C: "<<C<< endl;
13 //Xử lý một Circle như một Point (chỉ xem một phần lớp cơ sở) 14: PointPtr = &C;
15: cout << endl << "Circle C (via *PointPtr): "<<*PointPtr<<endl; 16 //Xử lý một Circle như một Circle
17: PointPtr = &C;
18: CirclePtr = (Circle *) PointPtr;
19: cout << endl << "Circle C (via *CirclePtr): " << endl
20: <<*CirclePtr<< endl << "Area of C (via CirclePtr): " 21: << CirclePtr->Area() << endl;
22: //Nguy hiểm: Xem một Point như một Circle 23: PointPtr = &P;
24: CirclePtr = (Circle *) PointPtr;
25: cout << endl << "Point P (via *CirclePtr): "<< endl
26: <<*CirclePtr<< endl << "Area of object CirclePtr points to: "
27: <<CirclePtr->Area() << endl; 28: return 0;
29: }
112
Hình 5.4: Kết quả của ví dụ 5.1
Trong định nghĩa lớp Point, các thành viên dữ liệu X và Y được chỉ định là protected, điều này cho phép các lớp dẫn xuất từ lớp Point truy cập trực tiếp các thành viên dữ liệu kế thừa. Nếu các thành viên dữ liệu này được chỉ định là private, các hàm thành viên public của Point phải được sử dụng để truy cập dữ liệu,
ngay cả bởi các lớp dẫn xuất.
Lớp Circle được kế thừa từ lớp Point với kế thừa public (ở dòng 7 file CIRCLE.H), tất cả các thành
viên của lớp Point được kế thừa thành lớp Circle. Điều này có nghĩa là giao diện public bao gồm các hàm
thành viên public của Point cũng như các hàm thành viên Area(), SetRadius() và GetRadius().
Constructor lớp Circle phải bao gồm constructor lớp Point để khởi động phần lớp cơ sở của đối tượng
lớp Circle ở dịng 7 file CIRCLE.CPP, dịng này có thể được viết lại như sau: Circle::Circle(float R, float A, float B)
: Point(A, B) //Gọi constructor của lớp cơ sở
Các giá trị A và B được chuyển từ constructor lớp Circle tới constructor lớp Point để khởi động các
thành viên X và Y của lớp cơ sở. Nếu constructor lớp Circle khơng bao gồm constructor lớp Point thì
constructor lớp Point gọi với các giá trị mặc định cho X và Y (nghĩa là 0 và 0). Nếu lớp Point khơng cung cấp một constructor mặc định thì trình biên dịch phát sinh lỗi.
Trong chương trình chính (file CT5_1.CPP) gán một con trỏ lớp dẫn xuất (địa chỉ của đối tượng C) cho con trỏ lớp cơ sở PointPtr và xuất đối tượng C của Circle bằng tốn tử chèn dịng của lớp Point (ở dịng 14 và 15). Chú ý rằng chỉ phần Point của đối tượng C của Circle được hiển thị. Nó ln ln đúng để gán một con trỏ lớp dẫn xuất cho con trỏ lớp cơ sở bởi vì một đối tượng lớp dẫn xuất là một đối tượng lớp cơ sở. Con trỏ lớp cơ sở chỉ trông thấy phần lớp cơ sở của đối tượng lớp dẫn xuất. Trình biên dịch thực hiện một chuyển
đổi ngầm của con trỏ lớp dẫn xuất cho một con trỏ lớp cơ sở.
Sau đó chương trình gán một con trỏ lớp dẫn xuất (địa chỉ của đối tượng C) cho con trỏ lớp cơ sở PointPtr và ép PointPtr trở về kiểu Circle *. Kết quả của ép kiểu được gán cho CirclePtr. Đối tượng C của
Circle được xuất bằng cách sử dụng toán tử chèn dịng của Circle. Diện tích của đối tượng C được xuất
thông qua CirclePtr. Các kết quả này là giá trị diện tích đúng bởi vì các con trỏ luôn luôn được trỏ tới một
đối tượng Circle (từ dịng 17 đến 22).
Kế tiếp, chương trình gán một con trỏ lớp cơ sở (địa chỉ của đối tượng P) cho con trỏ lớp cơ sở PointPtr và ép PointPtr trở về kiểu Circle *. Kết quả của ép kiểu được gán cho CirclePtr. Đối tượng P được xuất sử
dụng tốn tử chèn dịng của lớp Circle. Chú ý rằng giá trị xuất của thành viên Radius "kỳ lạ". Việc xuất một
Point như một Circle đưa đến một giá trị khơng hợp lệ cho Radius bởi vì các con trỏ luôn được trỏ đến một
đối tượng Point. Một đối tượng Point khơng có một thành viên Radius. Vì thế, chương trình xuất giá trị "rác" đối với thành viên dữ liệu Radius. Chú ý rằng giá trị của diện tích là 0.0 bởi vì tính tồn này dựa trên giá trị
không tồn tại của Radius (từ dòng 23 đến 27).Rõ ràng, truy cập các thành viên dữ liệu mà khơng phải ở đó thì nguy hiểm. Gọi các hàm thành viên mà không tồn tại có thể phá hủy chương trình.
Giáo trình mơn Lập trình hướng đối tượng Trang 113
II.4. Định nghĩa lại các thành viên lớp cơ sở trong một lớp dẫn xuất
Một lớp dẫn xuất có thể định nghĩa lại một hàm thành viên lớp cơ sở. Điều này được gọi là overriding. Khi hàm đó được đề cập bởi tên trong lớp dẫn xuất, phiên bản của lớp dẫn xuất được chọn một cách tự động. Tốn tử định phạm vi có thể sử dụng để truy cập phiên bản của lớp cơ sở từ lớp dẫn xuất.