📄 c++的多态性实现机制剖析.htm
字号:
<![if !mso]></td>
</tr>
</table>
<![endif]></v:textbox>
</v:shape><v:rect id="_x0000_s1093" style='position:absolute;left:0;
text-align:left;margin-left:309.6pt;margin-top:79.7pt;width:90pt;height:23.4pt;
text-indent:0;z-index:13'>
<v:textbox style='mso-next-textbox:#_x0000_s1093'>
<![if !mso]>
<table cellpadding=0 cellspacing=0 width="100%">
<tr>
<td><![endif]>
<div>
<p class=MsoNormal><span class=SpellE><span lang=EN-US>fish::<span
class=GramE>breathe</span></span></span><span class=GramE><span lang=EN-US>()</span></span></p>
</div>
<![if !mso]></td>
</tr>
</table>
<![endif]></v:textbox>
</v:rect><v:line id="_x0000_s1094" style='position:absolute;left:0;
text-align:left;z-index:14' from="255.6pt,89.75pt" to="309.6pt,89.75pt">
<v:stroke endarrow="block"/>
</v:line><![endif]--><![if !vml]><span style='mso-ignore:vglayout'>
<table cellpadding=0 cellspacing=0 align=left>
<tr>
<td width=77 height=5></td>
<td width=12></td>
<td width=449></td>
</tr>
<tr>
<td height=57></td>
<td colspan=2 align=left valign=top><img width=461 height=57
src="004/image003.gif" v:shapes="_x0000_s1081 _x0000_s1086 _x0000_s1087 _x0000_s1090"></td>
</tr>
<tr>
<td height=23></td>
</tr>
<tr>
<td height=57></td>
<td></td>
<td align=left valign=top><img width=449 height=57
src="004/image0004.gif" v:shapes="_x0000_s1091 _x0000_s1092 _x0000_s1093 _x0000_s1094"></td>
</tr>
</table>
</span><![endif]><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='text-indent:21.0pt;line-height:150%'><span
lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='text-indent:21.0pt;line-height:150%'><span
lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='line-height:150%'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='line-height:150%'><span lang=EN-US><o:p> </o:p></span></p>
<br style='mso-ignore:vglayout' clear=ALL>
<p class=MsoCaption align=center style='text-align:center'><span
style='font-family:黑体;mso-ascii-font-family:Arial'>图</span><span lang=EN-US>1- </span><!--[if supportFields]><span
lang=EN-US><span style='mso-element:field-begin'></span><span
style='mso-spacerun:yes'> </span>SEQ </span><span style='font-family:黑体;
mso-ascii-font-family:Arial'>图</span><span lang=EN-US>1- \* ARABIC <span
style='mso-element:field-separator'></span></span><![endif]--><span lang=EN-US><span
style='mso-no-proof:yes'>2</span></span><!--[if supportFields]><span
lang=EN-US><span style='mso-element:field-end'></span></span><![endif]--><span
lang=EN-US> animal</span><span style='font-family:黑体;mso-ascii-font-family:
Arial'>类和</span><span lang=EN-US>fish</span><span style='font-family:黑体;
mso-ascii-font-family:Arial'>类的虚表</span></p>
<p class=MsoNormal style='line-height:150%'><span lang=EN-US><span
style='mso-tab-count:1'> </span></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>那么如何定位虚表呢?编译器另外还为每个类的对象提供了一个虚表指针(即</span><span class=SpellE><span
lang=EN-US>vptr</span></span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化</span><span
class=SpellE><span lang=EN-US>vptr</span></span><span style='font-family:宋体;
mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>,从而让</span><span
class=SpellE><span lang=EN-US>vptr</span></span><span style='font-family:宋体;
mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。<span
class=GramE>对于例</span></span><span lang=EN-US>1-2</span><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>的程序,由于</span><span
class=SpellE><span lang=EN-US>pAn</span></span><span style='font-family:宋体;
mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>实际指向的对象类型是</span><span
lang=EN-US>fish</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>,因此</span><span class=SpellE><span
lang=EN-US>vptr</span></span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>指向的</span><span
lang=EN-US>fish</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>类的</span><span class=SpellE><span
lang=EN-US>vtable</span></span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>,当调用</span><span
class=SpellE><span lang=EN-US>pAn</span></span><span lang=EN-US>->breathe()</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>时,根据虚表中的函数地址找到的就是</span><span lang=EN-US>fish</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类的</span><span lang=EN-US>breathe()</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>函数。</span></p>
<p class=MsoNormal style='text-indent:21.0pt;line-height:150%'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数。那么虚表指针在什么时候,或者说在什么地方初始化呢?</span></p>
<p class=MsoNormal style='text-indent:21.0pt;line-height:150%'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>答案是在构造函数中进行虚表的创建和虚表指针的初始化。还记得构造函数的调用顺序吗,在构造子<span
class=GramE>类对象</span>时,要先<span class=GramE>调用父类的</span>构造函数,此时编译器只“看到了”父类,并不知道后面是否后还有继承者,它初始化<span
class=GramE>父类对象</span>的虚表指针,该虚表指针<span class=GramE>指向父类的</span>虚表。当执行子类的构造函数时,子<span
class=GramE>类对象</span>的虚表指针被初始化,指向自身的虚表。<span class=GramE>对于例</span></span><span
lang=EN-US>2-2</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>的程序来说,当</span><span lang=EN-US>fish</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类的</span><span class=SpellE><span lang=EN-US>fh</span></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>对象构造完毕后,其内部的虚表指针也就被初始化为指向</span><span lang=EN-US>fish</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类的虚表。在类型转换后,调用</span><span class=SpellE><span lang=EN-US>pAn</span></span><span
lang=EN-US>->breathe()</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>,由于</span><span
class=SpellE><span lang=EN-US>pAn</span></span><span style='font-family:宋体;
mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>实际指向的是</span><span
lang=EN-US>fish</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>类的对象,该对象内部的虚表指针指向的是</span><span
lang=EN-US>fish</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>类的虚表,因此最终调用的是</span><span lang=EN-US>fish</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类的</span><span lang=EN-US>breathe()</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>函数。</span></p>
<p class=MsoNormal style='text-indent:21.0pt;line-height:150%'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>要注意:对于虚函数调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是</span><span
lang=EN-US>C++</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>多态性实现的原理。</span></p>
<p class=MsoNormal style='text-indent:21.0pt;line-height:150%'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>总结(<span class=GramE>基类有</span>虚函数):</span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-18.0pt;line-height:
150%;mso-list:l1 level1 lfo3;tab-stops:list 39.0pt'><![if !supportLists]><span
lang=EN-US style='mso-fareast-font-family:"Times New Roman"'><span
style='mso-list:Ignore'>1、<span style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>每一个类都有虚表。</span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-18.0pt;line-height:
150%;mso-list:l1 level1 lfo3;tab-stops:list 39.0pt'><![if !supportLists]><span
lang=EN-US style='mso-fareast-font-family:"Times New Roman"'><span
style='mso-list:Ignore'>2、<span style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>虚表可以继承,如果子<span class=GramE>类没有</span>重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的<span
class=GramE>是基类的</span>虚函数实现。<span class=GramE>如果基类<span lang=EN-US
style='font-family:"Times New Roman"'>3</span>个</span>虚函数,<span class=GramE>那么基类的</span>虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。</span></p>
<p class=MsoNormal style='margin-left:39.0pt;text-indent:-18.0pt;line-height:
150%;mso-list:l1 level1 lfo3;tab-stops:list 39.0pt'><![if !supportLists]><span
lang=EN-US style='mso-fareast-font-family:"Times New Roman"'><span
style='mso-list:Ignore'>3、<span style='font:7.0pt "Times New Roman"'> </span></span></span><![endif]><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>派生类的虚表中虚函数地址的排列顺序和<span class=GramE>基类</span>的虚表中虚函数地址排列顺序相同。</span></p>
<h1 style='margin-left:21.25pt;text-indent:-21.25pt;mso-list:l0 level1 lfo1;
tab-stops:list 21.25pt'><![if !supportLists]><span lang=EN-US style='mso-fareast-font-family:
"Times New Roman"'><span style='mso-list:Ignore'>2.<span style='font:7.0pt "Times New Roman"'>
</span></span></span><![endif]><span lang=EN-US>VC</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>视频第三课</span><span lang=EN-US>this</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>指针说明</span></h1>
<p class=MsoNormal style='text-indent:21.0pt;line-height:150%'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>我在论坛的</span><span lang=EN-US>VC</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>教学视频版面发了帖子,是模拟</span><span lang=EN-US>MFC</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>类库的例子写的,主要是说明<span class=GramE>在基类的</span>构造函数中保存的</span><span
lang=EN-US>this</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>指针是指向子类的,我们在看一下这个例子:</span></p>
<div style='mso-element:para-border-div;border:none;border-bottom:solid windowtext 1.5pt;
padding:0cm 0cm 1.0pt 0cm'>
<p class=MsoCaption align=center style='text-align:center;border:none;
mso-border-bottom-alt:solid windowtext 1.5pt;padding:0cm;mso-padding-alt:0cm 0cm 1.0pt 0cm'><span
style='font-family:黑体;mso-ascii-font-family:Arial'>例</span><span lang=EN-US>1- </span><!--[if supportFields]><span
lang=EN-US><span style='mso-element:field-begin'></span><span
style='mso-spacerun:yes'> </span>SEQ </span><span style='font-family:黑体;
mso-ascii-font-family:Arial'>例</span><span lang=EN-US>1- \* ARABIC <span
style='mso-element:field-separator'></span></span><![endif]--><span lang=EN-US><span
style='mso-no-proof:yes'>3</span></span><!--[if supportFields]><span
lang=EN-US><span style='mso-element:field-end'></span></span><![endif]--></p>
</div>
<p class=MsoNormal><span lang=EN-US>#include <<span class=SpellE>iostream.h</span>></span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span class=GramE><span lang=EN-US>class</span></span><span
lang=EN-US> base;</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span class=GramE><span lang=EN-US>base</span></span><span
lang=EN-US> * <span class=SpellE>pbase</span>;</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span class=GramE><span lang=EN-US>class</span></span><span
lang=EN-US> base</span></p>
<p class=MsoNormal><span lang=EN-US>{</span></p>
<p class=MsoNormal><span class=GramE><span lang=EN-US>public</span></span><span
lang=EN-US>:</span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:1'> </span><span
class=GramE>base()</span></span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:1'> </span>{</span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:2'> </span><span
class=SpellE><span class=GramE>pbase</span></span><span class=GramE>=</span>this;</span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:2'> </span></span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:1'> </span>}</span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:1'> </span><span
class=GramE>virtual</span> void fn()</span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:1'> </span>{</span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:2'> &nbs
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -