⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch12.htm

📁 vc的电子书
💻 HTM
📖 第 1 页 / 共 5 页
字号:
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 &lt;iostream.h&gt;
4:
5:     class Mammal
6:     {
7:     public:
8:        Mammal():itsAge(1) { cout &lt;&lt; &quot;Mammal constructor...\n&quot;; }
9:        ~Mammal() { cout &lt;&lt; &quot;Mammal destructor...\n&quot;; }
10:       void Move() const { cout &lt;&lt; &quot;Mammal move one step\n&quot;; }
11:       virtual void Speak() const { cout &lt;&lt; &quot;Mammal speak!\n&quot;; }
12:    protected:
13:       int itsAge;
14:
15:    };
16:
17:    class Dog : public Mammal
18:    {
19:    public:
20:       Dog() { cout &lt;&lt; &quot;Dog Constructor...\n&quot;; }
21:       ~Dog() { cout &lt;&lt; &quot;Dog destructor...\n&quot;; }
22:       void WagTail() { cout &lt;&lt; &quot;Wagging Tail...\n&quot;; }
23:       void Speak()const { cout &lt;&lt; &quot;Woof!\n&quot;; }
24:       void Move()const { cout &lt;&lt; &quot;Dog moves 5 steps...\n&quot;; }
25:    };
26:
27:    int main()
28:    {
29:
30:       Mammal *pDog = new Dog;
31:       pDog-&gt;Move();
32:       pDog-&gt;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 &lt;iostream.h&gt;
4:
5:     class Mammal
6:     {
7:     public:
8:        Mammal():itsAge(1) {  }
9:        ~Mammal() { }
10:       virtual void Speak() const { cout &lt;&lt; &quot;Mammal speak!\n&quot;; }
11:    protected:
12:       int itsAge;
13:    };
14:
15:    class Dog : public Mammal
16:    {
17:    public:
18:       void Speak()const { cout &lt;&lt; &quot;Woof!\n&quot;; }
19:    };
20:
21:
22:    class Cat : public Mammal
23:    {
24:    public:
25:       void Speak()const { cout &lt;&lt; &quot;Meow!\n&quot;; }
26:    };
27:
28:
29:    class Horse : public Mammal
30:    {
31:    public:
32:       void Speak()const { cout &lt;&lt; &quot;Winnie!\n&quot;; }
33:    };
34:
35:    class Pig : public Mammal
36:    {
37:    public:
38:       void Speak()const { cout &lt;&lt; &quot;Oink!\n&quot;; }
39:    };
40:
41:    int main()
42:    {
43:       Mammal* theArray[5];
44:       Mammal* ptr;
45:       int choice, i;
46:       for ( i = 0; i&lt;5; i++)
47:       {
48:          cout &lt;&lt; &quot;(1)dog (2)cat (3)horse (4)pig: &quot;;
49:          cin &gt;&gt; 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&lt;5;i++)
66:          theArray[i]-&gt;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="12zcp02.jpg" tppabs="http://www.mcp.com/814147200/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="12zcp03.jpg" tppabs="http://www.mcp.com/814147200/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, &quot;Special Classes and Functions.&quot;) 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="12zcp04.jpg" tppabs="http://www.mcp.com/814147200/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 &quot;real&quot; 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
are covered on Day 20, &quot;Exceptions and Error Handling.&quot;
<H4 ALIGN="CENTER"><A NAME="Heading40"></A><FONT COLOR="#000077">Slicing</FONT></H4>
<P>Note that the virtual function magic operates only on pointers and references.
Passing an object by value will not enable the virtual functions to be invoked. Listing
12.10 illustrates this problem.</P>
<P><A NAME="Heading41"></A><FONT SIZE="4" COLOR="#000077"><B>Listing 12.10. Data
slicing when passing by value.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">1:      //Listing 12.10 Data slicing with passing by value
2:
3:      #include &lt;iostream.h&gt;
4:
5:     enum BOOL { FALSE, TRUE };
6:     class Mammal
7:     {
8:     public:
9:        Mammal():itsAge(1) {  }
10:       ~Mammal() { }
11:       virtual void Speak() const { cout &lt;&lt; &quot;Mammal speak!\n&quot;; }
12:    protected:
13:       int itsAge;
14:    };
15:
16:    class Dog : public Mammal
17:    {

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -