.É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 án - Bài giảng: TÀI LIỆU C++ (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>

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 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.

Một phần của tài liệu Giáo án - Bài giảng: TÀI LIỆU C++ (Trang 109 - 113)