Ép kiểu các con trỏ lớp cơ sở tới các con trỏ lớp dẫn xuất

Một phần của tài liệu Giáo trình lập trình hướng đối tượng (Trang 109 - 113)

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>

4: #include "point.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: {

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: }

Chúng ta chạy ví dụ 5.1, kết quả ở hình 5.4

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 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()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 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 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 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 toá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ó luôn luôn đú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 toá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 toà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.

113

Một phần của tài liệu Giáo trình lập trình hướng đối tượng (Trang 109 - 113)

Tải bản đầy đủ (PDF)

(165 trang)