📄 ch12.htm
字号:
16: int itsAge;
17: int itsWeight;
18: };
19:
20: class Dog : public Mammal
21: {
22: public:
23: void Move()const;
24:
25: };
26:
27: void Dog::Move() const
28: {
29: cout << "In dog move...\n";
30: Mammal::Move(3);
31: }
32:
33: int main()
34: {
35: Mammal bigAnimal;
36: Dog fido;
37: bigAnimal.Move(2);
38: fido.Mammal::Move(6);
39: return 0;
<TT>40: }</TT></FONT>
<FONT COLOR="#0066FF">
Output: Mammal move 2 steps.
Mammal move 6 steps.
</FONT></PRE>
<P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>On line 35, a <TT>Mammal</TT>,
<TT>bigAnimal</TT>, is created, and on line 36, a <TT>Dog</TT>, <TT>fido</TT>, is
created. The method call on line 37 invokes the <TT>Move()</TT> method of <TT>Mammal</TT>,
which takes an <TT>int</TT>.</P>
<P>The programmer wanted to invoke <TT>Move(int)</TT> on the <TT>Dog</TT> object,
but had a problem. <TT>Dog</TT> overrides the <TT>Move()</TT> method, but does not
overload it and does not provide a version that takes an <TT>int</TT>. This is solved
by the explicit call to the base class <TT>Move(int)</TT> method on line 33.
<BLOCKQUOTE>
<P>
<HR>
<B>DO</B> extend the functionality of tested classes by deriving. <B>DO</B> change
the behavior of certain functions in the derived class by overriding the base class
methods. <B>DON'T</B> hide a base class function by changing the function signature.
<HR>
</BLOCKQUOTE>
<H3 ALIGN="CENTER"><A NAME="Heading30"></A><FONT COLOR="#000077">Virtual Methods</FONT></H3>
<P>This chapter has emphasized the fact that a <TT>Dog</TT> object is a <TT>Mammal</TT>
object. So far that has meant only that the <TT>Dog</TT> object has inherited the
attributes (data) and capabilities (methods) of its base class. In C++ the is-a relationship
runs deeper than that, however.</P>
<P>C++ extends its polymorphism to allow pointers to base classes to be assigned
to derived class objects. Thus, you can write</P>
<PRE><FONT COLOR="#0066FF">Mammal* pMammal = new Dog;
</FONT></PRE>
<P>This creates a new <TT>Dog</TT> object on the heap and returns a pointer to that
object, which it assigns to a pointer to <TT>Mammal</TT>. This is fine, because a
dog is a mammal.
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>This is the essence of polymorphism.
For example, you could create many different types of windows, including dialog boxes,
scrollable windows, and list boxes, and give them each a virtual <TT>draw()</TT>
method. By creating a pointer to a window and assigning dialog boxes and other derived
types to that pointer, you can call <TT>draw()</TT> without regard to the actual
run-time type of the object pointed to. The correct <TT>draw()</TT> function will
be called.
<HR>
</BLOCKQUOTE>
<P>You can then use this pointer to invoke any method on <TT>Mammal</TT>. What you
would like is for those methods that are overridden in <TT>Dog()</TT> to call the
correct function. Virtual functions let you do that. Listing 12.8 illustrates how
this works, and what happens with non-virtual methods.</P>
<P><A NAME="Heading31"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 12.8. Using
virtual methods.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">1: //Listing 12.8 Using virtual methods
2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
7: public:
8: Mammal():itsAge(1) { cout << "Mammal constructor...\n"; }
9: ~Mammal() { cout << "Mammal destructor...\n"; }
10: void Move() const { cout << "Mammal move one step\n"; }
11: virtual void Speak() const { cout << "Mammal speak!\n"; }
12: protected:
13: int itsAge;
14:
15: };
16:
17: class Dog : public Mammal
18: {
19: public:
20: Dog() { cout << "Dog Constructor...\n"; }
21: ~Dog() { cout << "Dog destructor...\n"; }
22: void WagTail() { cout << "Wagging Tail...\n"; }
23: void Speak()const { cout << "Woof!\n"; }
24: void Move()const { cout << "Dog moves 5 steps...\n"; }
25: };
26:
27: int main()
28: {
29:
30: Mammal *pDog = new Dog;
31: pDog->Move();
32: pDog->Speak();
33:
34: return 0;
<TT>35: }</TT></FONT>
<FONT COLOR="#0066FF">
Output: Mammal constructor...
Dog Constructor...
Mammal move one step
Woof!
</FONT></PRE>
<P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>On line 11, <TT>Mammal</TT>
is provided a virtual method--<TT>speak()</TT>. The designer of this class thereby
signals that she expects this class eventually to be another class's base type. The
derived class will probably want to override this function.</P>
<P>On line 30, a pointer to <TT>Mammal</TT> is created (<TT>pDog</TT>), but it is
assigned the address of a new <TT>Dog</TT> object. Because a dog is a mammal, this
is a legal assignment. The pointer is then used to call the <TT>Move()</TT> function.
Because the compiler knows <TT>pDog</TT> only to be a <TT>Mammal</TT>, it looks to
the <TT>Mammal</TT> object to find the <TT>Move()</TT> method.</P>
<P>On line 32, the pointer then calls the <TT>Speak()</TT> method. Because <TT>Speak()</TT>
is virtual, the <TT>Speak()</TT> method overridden in <TT>Dog</TT> is invoked.</P>
<P>This is almost magical. As far as the calling function knew, it had a <TT>Mammal</TT>
pointer, but here a method on <TT>Dog</TT> was called. In fact, if you had an array
of pointers to <TT>Mammal</TT>, each of which pointed to a subclass of <TT>Mammal</TT>,
you could call each in turn and the correct function would be called. Listing 12.9
illustrates this idea.</P>
<P><A NAME="Heading33"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 12.9. Multiple
virtual functions called in turn.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">1: //Listing 12.9 Multiple virtual functions called in turn
2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
7: public:
8: Mammal():itsAge(1) { }
9: ~Mammal() { }
10: virtual void Speak() const { cout << "Mammal speak!\n"; }
11: protected:
12: int itsAge;
13: };
14:
15: class Dog : public Mammal
16: {
17: public:
18: void Speak()const { cout << "Woof!\n"; }
19: };
20:
21:
22: class Cat : public Mammal
23: {
24: public:
25: void Speak()const { cout << "Meow!\n"; }
26: };
27:
28:
29: class Horse : public Mammal
30: {
31: public:
32: void Speak()const { cout << "Winnie!\n"; }
33: };
34:
35: class Pig : public Mammal
36: {
37: public:
38: void Speak()const { cout << "Oink!\n"; }
39: };
40:
41: int main()
42: {
43: Mammal* theArray[5];
44: Mammal* ptr;
45: int choice, i;
46: for ( i = 0; i<5; i++)
47: {
48: cout << "(1)dog (2)cat (3)horse (4)pig: ";
49: cin >> choice;
50: switch (choice)
51: {
52: case 1: ptr = new Dog;
53: break;
54: case 2: ptr = new Cat;
55: break;
56: case 3: ptr = new Horse;
57: break;
58: case 4: ptr = new Pig;
59: break;
60: default: ptr = new Mammal;
61: break;
62: }
63: theArray[i] = ptr;
64: }
65: for (i=0;i<5;i++)
66: theArray[i]->Speak();
67: return 0;
<TT>68: }</TT></FONT>
<FONT COLOR="#0066FF">
Output: (1)dog (2)cat (3)horse (4)pig: 1
(1)dog (2)cat (3)horse (4)pig: 2
(1)dog (2)cat (3)horse (4)pig: 3
(1)dog (2)cat (3)horse (4)pig: 4
(1)dog (2)cat (3)horse (4)pig: 5
Woof!
Meow!
Winnie!
Oink!
Mammal speak!
</FONT></PRE>
<P><FONT COLOR="#000077"><B>Analysis:</B></FONT><B> </B>This stripped-down program,
which provides only the barest functionality to each class, illustrates virtual functions
in their purest form. Four classes are declared; <TT>Dog</TT>,<TT> Cat</TT>,<TT>
Horse</TT>, and <TT>Pig</TT> are all derived from <TT>Mammal</TT>.</P>
<P>On line 10, <TT>Mammal</TT>'s <TT>Speak()</TT> function is declared to be virtual.
On lines 18, 25, 32, and 38, the four derived classes override the implementation
of <TT>Speak()</TT>.</P>
<P>The user is prompted to pick which objects to create, and the pointers are added
to the array on lines 46-64.
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE: </B></FONT>At compile time, it is impossible to know
which objects will be created, and thus which <TT>Speak()</TT> methods will be invoked.
The pointer <TT>ptr</TT> is bound to its object at runtime. This is called dynamic
binding, or run-time binding, as opposed to static binding, or compile-time binding.
<HR>
</BLOCKQUOTE>
<H4 ALIGN="CENTER"><A NAME="Heading35"></A><FONT COLOR="#000077">How Virtual Functions
Work</FONT></H4>
<P>When a derived object, such as a <TT>Dog</TT> object, is created, first the constructor
for the base class is called and then the constructor for the derived class is called.
Figure 12.2 shows what the <TT>Dog</TT> object looks like after it is created. Note
that the <TT>Mammal</TT> part of the object is contiguous in memory with the <TT>Dog</TT>
part.<BR>
<BR>
<A NAME="Heading36"></A><A HREF="javascript:if(confirm('http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp02.jpg \n\nThis file was not retrieved by Teleport Pro, because the server reports that this file cannot be found. \n\nDo you want to open it from the server?'))window.location='http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp02.jpg'" tppabs="http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp02.jpg"><FONT COLOR="#000077">Figure
12.2.</FONT></A><FONT COLOR="#000077"> </FONT><I>The <TT>Dog</TT> object after it
is created.</I> <BR>
<BR>
When a virtual function is created in an object, the object must keep track of that
function. Many compilers build a virtual function table, called a v-table. One of
these is kept for each type, and each object of that type keeps a virtual table pointer
(called a <TT>vptr</TT> or v-pointer), which points to that table.</P>
<P>While implementations vary, all compilers must accomplish the same thing, so you
won't be too wrong with this description.<BR>
<BR>
<A NAME="Heading37"></A><A HREF="javascript:if(confirm('http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp03.jpg \n\nThis file was not retrieved by Teleport Pro, because the server reports that this file cannot be found. \n\nDo you want to open it from the server?'))window.location='http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp03.jpg'" tppabs="http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp03.jpg"><FONT COLOR="#000077">Figure
12.3.</FONT></A><FONT COLOR="#000077"> </FONT><I>The v-table of a <TT>Mammal</TT>.</I><BR>
<BR>
Each object's <TT>vptr</TT> points to the v-table which, in turn, has a pointer to
each of the virtual functions. (Note, pointers to functions will be discussed in
depth on Day 14, "Special Classes and Functions.") When the <TT>Mammal</TT>
part of the <TT>Dog</TT> is created, the <TT>vptr</TT> is initialized to point to
the correct part of the v-table, as shown in Figure 12.3.<BR>
<BR>
<A NAME="Heading38"></A><A HREF="javascript:if(confirm('http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp04.jpg \n\nThis file was not retrieved by Teleport Pro, because the server reports that this file cannot be found. \n\nDo you want to open it from the server?'))window.location='http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp04.jpg'" tppabs="http://petunia.atomki.hu/pio/Manuals/english/0-672/0-672-31070-8/art/ch12/12zcp04.jpg"><FONT COLOR="#000077">Figure
12.4.</FONT></A><FONT COLOR="#000077"> </FONT>The v-table of a <TT>Dog</TT>.<BR>
<BR>
When the <TT>Dog</TT> constructor is called, and the <TT>Dog</TT> part of this object
is added, the <TT>vptr</TT> is adjusted to point to the virtual function overrides
(if any) in the <TT>Dog</TT> object (see Figure 12.4) .</P>
<P>When a pointer to a <TT>Mammal</TT> is used, the <TT>vptr</TT> continues to point
to the correct function, depending on the "real" type of the object. Thus,
when <TT>Speak()</TT> is invoked, the correct function is invoked.
<H4 ALIGN="CENTER"><A NAME="Heading39"></A><FONT COLOR="#000077">You Cant Get There
from Here</FONT></H4>
<P>If the <TT>Dog</TT> object had a method, <TT>WagTail()</TT>, which is not in the
<TT>Mammal</TT>, you could not use the pointer to <TT>Mammal</TT> to access that
method (unless you cast it to be a pointer to <TT>Dog</TT>). Because <TT>WagTail()</TT>
is not a virtual function, and because it is not in a <TT>Mammal</TT> object, you
can't get there without either a <TT>Dog</TT> object or a <TT>Dog</TT> pointer.</P>
<P>Although you can transform the <TT>Mammal</TT> pointer into a <TT>Dog</TT> pointer,
there are usually far better and safer ways to call the <TT>WagTail()</TT> method.
C++ frowns on explicit casts because they are error-prone. This subject will be addressed
in depth when multiple inheritance is covered tomorrow, and again when templates
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -