当前位置: 首页 > 文档资料 > C++大学教程 >

9.4 把基类指针强制转换为派生类指针

优质
小牛编辑
141浏览
2023-12-01

公有派生类的对象可作为其相应基类的对象处理,这使得一些有意义的操作成为可能。例如,从某个特定基类派生出来的各种类,尽管这些类的对象彼此之间互不相同,但是仍然能够建立这些对象的链表,只要把这些对象作为基类对象处理就可以了。然而反过来是不行的,基类的对象不能自动成为派生类的对象。

常见编程错误 9.1
将基类对象作为派生类对象处理。

程序员可以用显式类型转换把基类指针强制转换为派生类指针。但是,如果要复引用该指针,那么在转换前首先应该把它指向某个派生类的对象,这一点要小心。本节采用大多数编译器中常用的方法。第21章将介绍符合 ANSI/ISO C++ 草案标准的最新编译器的新特性,包括运行时类型信息(RTFI)、dynamic_cast 和 typeid。

常见编程错误 9.2
把指向基类对象的指针显式地强制转换为派生类指针,然后引用该对象中并不存在的派生类的成更会导
致运行时的逻辑错误。

第一个例子见图 9.4。第1行到第39行是类Point的定义和其成员函数的定义,第40行到第94行是类 Circle 的定义和其成员函数的定义,第95行到第132行是类的驱动程序,该程序演示了如何把派生类指针赋给基类指针和如何把基类指针强制转换为派生类指针。余下的部分是程序输出。

首先看一下类Point的定义。Point 的 public 接口包含成员函数 setPoint、getX 和 getY。Point 的数据成员x和y指定为 protected,从而在防止了 Point 对象的用户直接访问这些数据的同时,又能够让派生类直接访问继承来的数据成员。

如果将数据成员指定为private,那么就要用Point的public成员函数甚至派生类来访问这些数据。注意,由于重载的流插入运算符函数是类 Point 的友元,所以 Point 重载流插入函数能够直接引用变量x和y。因为重载的流插入运算符函数不是类Point的成员函数,所以需要通过对象来引用变量x和y(即p.x和p.y)。注意这个类提供内联的 publiic 成员函数 getX 和 getY,因此 operator<< 不必成为友元就可以达到良好性能。但所需public成员函数不一定在每个类的 public 接口中提供,因此最好还是建立友元。

1 // Fig. 9.4: point.h
2 // Definition of class Point
3 #ifndef POINT H
4 #define POINT H
5
6 class Point {
7 friend ostream &operator<<( ostream &, const Point & );
8 public:
9 Point( int = 0, int = 0 ); // default constructor
10 void setPoint( int, int ); // set coordinates
11 int getX() const { return x; } // get x coordinate
12 int getY() const/{/ return y; } // get y coordinate
13 protected: accessible by derived classes
14 int x, y; // x and y coordinates of the Point
15 };
16
17 #endif
18 // Fig. 9.4: point.cpp
19 // Member functions for class Point
20 #include <iostream.h>
21 #include "point.h"
22
23 // Constructor for class Point
24 Point::Point( int a, int b ) { setPoint( a, b ); }
25
26 // Set x and y coordinates of Point
27 void Point::setPoint{ int a, int b )
28 {
29
30 y = b;
31 }
32
33 // output Point (with overloaded stream insertion operator)
34 ostream &operator<<{ ostream &output, const Point &p )
35 {
36 output << '[' << p.x << ", "<< p.y << ']';
37
38 return output; // enables cascaded calls
39 }
40 // Fig. 9.4: circle.h
41 // Definition of class Circle
42 #ifndef CIRCLE_H
43 #define CIRCLE_H
44
45 #include <iostream.h>
46 #include <iomanip.h>
47 #include "point.h"
48
49 class Circle : public Point { // Circle inherits from Point
50 friend ostream &operator<<( ostream &, const Circle & );
51 public:
52 // default constructor
53 Circle( double r = 0.0, int x = O, int y = 0 );
54
55 void setRadius( double ); // set radius
56 double getRadius() const; // return radius
57 double area() const; // calculate area
58 protected:
59 double radius;
60 };
61
62 #endif
63 // Fig. 9.4:circle.cpp
64 // Member function definitions for class Circle
65 #include "circle.h"
66
67 // Constructor for Circle calls constructor for Point
68 // with a member initializer then initializes radius.
69 Circle::Circle( double r, int a, int b )
70 : Point( a, b ) // call base-class constructor
71 { setRadius( r ); }
72
73 // Set radius of Circle
74 void Circle::setRadius( double r )
75 { radius = ( r >= O ? r : 0 ); }
76
77 // Get radius of Circle
78 double Circle::getRadius() const { return radius; }
79
80 // Calculate area of Circle
81 double circle::area() const
82 { return 3.14159 * radius * radius; }
83
84 // Output a Circle in the form:
86 ostream &operator<<( ostream &output, const Circle &c )
87 {
88 output << "Center =" << static cast< Point >( c )
89 << "; Radius ="
90 << setiosflags( ios::fixed | ios::showpoint )
91 << setprecision( 2 ) << c.radius;
92
93 return output; // enables cascaded calls
94 }
95 // Fig. 9.4:fig09 04.cpp
96 // Casting base-class pointers to derived-class pointers
97 #include <iostream.h>
98 #include <iomanip.h>
99 #include "point.h"
100 #include "circle.h"
101
102 int main()
103 {
104 Point *pointPtr = 0, p( 30, 50 );
105 Circle *circlePtr = 0, c( 2.7, 120, 89 );
106
107 cout << "Point p: "<< p << ,\nCircle C: "<< c << '\n';
108
109 // Treat a Circle as a Point (see only the base class part)
110 pointPtr = &C; // assign address of Circle to pointPtr
111 cout << "\nCircle C (via *pointPtr):"
112 << *pointPtr << '\n';
113
114 // Treat a Circle as a Circle (with some Casting)
115 pointPtr = &C; // assign address of Circle to pointPtr
116
117 // cast base-class pointer to derived-class pointer
118 circlePtr = static cast< Circle * >( pointPtr );
119 cout << "\nCircle C (via *circlePtr):\n" << *circlePtr
120 << "\nArea of C (via circlePtr):"
121 << circlePtr->area() << '\n';
122
123 // DANGEROUS: Treat a Point as a Circle
124 pointPtr = &p; // assign address of Point to pointPtr
125
126 // cast base-class pointer to derived-class pointer
127 circlePtr = static_cast< Circle * >( pointPtr );
128 cout << "\nPoint p (via *circlePtr):\n" << *circlePtr
129 << "\nArea of object circlePtr points to:"
130 << circlePtr->area() << endl;
131 return 0;
132 }

输出结果:

Point p: [ 30, 50]
Circle c: Center = [ 120, 89]; Radius = 2.70

Circle c(via *circlePtr):[ 120,89 ]

Circle c( via *circlePtr ):
Center = [ 120,89] ;Radius = 2.70
Area of c (via circlePtr): 22.90

oint p( via *circlePtr ):
Center = [ 30, 50]; Radius = 0.00
Area of object circlePtr points to: 0.00

图 9.4 把基类指针强制转换为派生类指针

类 Circle 继承了类 Point,类定义的第一行指定了这种继承是 public 继承:

class Circle : public Point { // Circle inherits from Point

Point 和 Circle 重载的流插入运算符输出了这两个对象的信息。然后,驱动程序将派生类指针(对象c的地址)赋绐基类指针pointPtr并用Point的operator<<输出Circle的对象c,并复引用指针 *pointPtr。

注意只显示Circle对象c的Point部分。对public继承,总是可以将派生类指针赋给基类,因为派生类对象也是基类对象。基类指针只“看到”派生类对象的基类部分。编译器进行派生类指针向基类指针的隐式转换。

随后,程序将派生类指针(对象c的地址)赋给基类指针 pointPtr,并将 pointPtr 强制转换回 Circle* 类型,强制转换后的结果赋给指针circlePtr。使用Circle重载流插入运算符输出Circle的对象c并复引用指针 *circlePtr。然后通过指针 circlePtr 输出 Circle 对象c的面积。因为该指针一直指向Circle对象,所以输出了该对象的合法面积值。
因为把基类指针直接赋给派生类指针蕴含着危险性,所以编译器不允许这么做,也不执行隐式转换。使用显式类型转换是告诉编译器程序员已经知道了这种危险性。正确地使用指针是程序员的责任,因此编译器允许有危险的转换。

接着,程序演示了将基类指引(对象p的地址)赋给基类指针 pointPtr,并将 pointPtr 强制转换为Circle*类型,强制转换操作的结果赋给了circlePtr。Point对象p用Circle的 operator<< 输出,并复引用指针 *circlePtr。注意半径元素输出为0(实际上不存在,因为circlePtr实际上针对Point对象)。将Point作为Circle输出就会导致radius为未定义的值(这里刚好为0),因为指针总是指向 Point 对象。

Point对象没有radius成员,因此输出 circlePtr 所指 radius 数据成员内存地址中的值。circlePtr 所指对象的面积(Point 对象P)也是通过 circlePtr 输出。注意面积值为 0.00,这是根据 radius 未定义 的值算出的。显然,访问不存在的数据成员是很危险的。调用不存在的成员函数可能使程序崩溃。

本节介绍指针转换的机制。为下一章介绍多态与面向对象编程打下了基础。