热度 23
2012-4-13 21:23
1697 次阅读|
0 个评论
The is a relationship Derivation defines an "is a" or "is a kind of" relationship between the derived and base class. That is, a circle is a shape, a rectangle is a shape, and a triangle is a shape. This relationship is only in the direction of the arrows in the diagram—from derived to base, but not from base to derived or from derived to derived. For example, a shape is not necessarily a circle, and a circle is certainly not a rectangle. In C++, the is a relationship manifests itself as a built-in conversion from "pointer to derived class" into "pointer to base class". This lets you create a collection of assorted shapes as an array of pointer to shapes, and populate that array with pointers to circles, rectangles, and triangles. For example: shape *sc ; ~~~ sc = new circle (2); sc = new triangle (5, 6, asin(0.8)); sc = new rectangle (3, 4); sc = new circle (3); Here, the new-expression: new circle (2) creates a circle initialized with radius 2 and returns the address of that circle as a value of type "pointer to circle". The assignment: sc = new circle (2); assigns that "pointer to circle" to a "pointer to shape", but this is fine because a circle is a shape. Last month, I presented a function called largest which determines the shape with the largest area in a collection of shapes. Here is a version of that function, written in C++ to work with the hierarchy of shapes: shape const *largest(shape const *s -area(); if (area max) { max = area; p = s ; } } return p; } Here, s is a pointer to a shape in the collection. The expression s -area() is supposed to call the area function for each shape. Unfortunately, with the class definitions as is, it doesn't yet work correctly. Here's why... Static vs. dynamic binding Again, an assignment such as: sc = new circle (2); assigns a "pointer to circle" to a "pointer to shape". Such assignments make it harder to talk about the type of sc : it's declared to point to a shape, but it actually points to a circle. Later in the same program, another assignment such as: sc = new rectangle (3, 4); assigns a "pointer to rectangle" to a "pointer to shape". Pointer sc is still declared to point to a shape, but now it points to a rectangle. Here's some terminology that makes it easier to discuss such schizophrenic behaviour. The static type of an expression is its type based on the declared type of the operands in that expression. The compiler determines an expression's static type during compilation. That type doesn't change as long as the source program doesn't change. The dynamic type of an expression is the type of the object that the expression actually designates at some point during program execution. The dynamic type of an expression may change during execution. For example, the expression sc always has static type "pointer to shape". After the assignment: sc = new circle (2); the expression sc still has static type "pointer to shape", but now it has dynamic type "pointer to circle". After the assignment: sc = new rectangle (3, 4); the expression sc still has static type "pointer to shape", but now it has dynamic type "pointer to rectangle". Now, here's why the largest function doesn't yet work correctly. The expression s -area() is supposed to compute the area based on the dynamic type of s . That is, if s actually points to a circle, then s -area() should call the circle's area function. If s actually points to a rectangle, then s -area() should call the rectangle's area function. And so on. Unfortunately, it doesn't work that way, yet. By default, C++ uses static binding for member function calls. That is, s has static type "pointer to shape", so s -area() always calls the area function for the static type of *s , which is the base class shape. However, the largest function needs the call to use dynamic binding: s -area() should call the area function for the dynamic type of *s , be it circle, rectangle, triangle, or any type derived from shape. Virtual functions To get the area functions in all the shapes to bind dynamically, all you have to do is declare area as a virtual function in the base class: class shape { public: shape(); // constructor virtual double area() const; virtual double perimeter() const; ~~~ }; I want the perimeter function to be dynamically bound as well, so I declared it virtual, too. That's all I had to do. Now the largest function works just as it should. The "virtual" property is an inherited trait: a function in a derived class with the same name and parameter type(s) as a function in its base class is virtual by default. That is, in: class rectangle: public shape { public: rectangle(double h, double w); double area() const; ~~~ }; the area function is virtual, even though it doesn't say so explicitly. If you prefer to be explicit, you can write: class rectangle: public shape { public: rectangle(double h, double w); virtual double area() const; ~~~ }; and it behaves just the same. A lot more to come I'll have more to say in future columns about how virtual functions work and how you can emulate them in C.