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

📄 9336.htm

📁 C++细节解释
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<HTML>
<HEAD>
<meta http-equiv='Content-Type' content='text/html; charset=gb2312'>


<style >
.fst{padding:0px 15px;width:770px;border-left:0px solid #000000;border-right:0px solid #000000}
.fstdiv3 img{border:0px;border-right:8px solid #eeeecc;border-top:6px solid #eeeecc}
</style>
<title>
Effective C++ 2e Item39
</title>
</HEAD>
<BODY >
<center>

<div align=center><div class=fst align=left><div class=fstdiv3 id=print2>
<b>
Effective C++ 2e Item39</b> 
<P>条款39: 避免 "向下转换" 继承层次</P> 
<P>在当今喧嚣的经济时代,关注一下我们的金融机构是个不错的主意。所以,看看下面这个有关银行帐户的协议类(Protocol class )(参见条款34):</P> 
<P>class Person { ... };</P> 
<P>class BankAccount {<BR>public:<BR>&nbsp; BankAccount(const Person *primaryOwner,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const Person *jointOwner);<BR>&nbsp; virtual ~BankAccount();</P> 
<P>&nbsp; virtual void makeDeposit(double amount) = 0;<BR>&nbsp; virtual void makeWithdrawal(double amount) = 0;</P> 
<P>&nbsp; virtual double balance() const = 0;</P> 
<P>&nbsp; ...</P> 
<P>};</P> 
<P>很多银行现在提供了多种令人眼花缭乱的帐户类型,但为简化起见,我们假设只有一种银行帐户,称为存款帐户:</P> 
<P>class SavingsAccount: public BankAccount {<BR>public:<BR>&nbsp; SavingsAccount(const Person *primaryOwner,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const Person *jointOwner);<BR>&nbsp; ~SavingsAccount();</P> 
<P>&nbsp; void creditInterest();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 给帐户增加利息</P> 
<P>&nbsp; ...</P> 
<P>};</P> 
<P>这远远称不上是一个真正的存款帐户,但还是那句话,现在什么年代?至少,它满足我们现在的需要。</P> 
<P>银行想为它所有的帐户维持一个列表,这可能是通过标准库(参见条款49)中的list类模板实现的。假设列表被叫做allAccounts:</P> 
<P>list&lt;BankAccount*&gt; allAccounts;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 银行中所有帐户</P> 
<P>和所有的标准容器一样,list存储的是对象的拷贝,所以,为避免每个BankAccount存储多个拷贝,银行决定让allAccounts保存BankAccount的指针,而不是BankAccount本身。</P> 
<P>假设现在准备写一段代码来遍历所有的帐户,为每个帐户计算利息。你会这么写:</P> 
<P>// 不能通过编译的循环(如果你以前从没<BR>// 见过使用 "迭代子" 的代码,参见下文)<BR>for (list&lt;BankAccount*&gt;::iterator p = allAccounts.begin();<BR>&nbsp;&nbsp;&nbsp;&nbsp; p != allAccounts.end();<BR>&nbsp;&nbsp;&nbsp;&nbsp; ++p) {</P> 
<P>&nbsp; (*p)-&gt;creditInterest();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 错误!</P> 
<P>}</P> 
<P>但是,编译器很快就会让你认识到:allAccounts包含的指针指向的是BankAccount对象,而非SavingsAccount对象,所以每次循环,p指向的是一个BankAccount。这使得对creditInterest的调用无效,因为creditInterest只是为SavingsAccount对象声明的,而不是BankAccount。</P> 
<P>如果"list&lt;BankAccount*&gt;::iterator p = allAccounts.begin()" 在你看来更象电话线中的噪音,而不是C++,那很显然,你以前无缘见识过C++标准库中的容器类模板。标准库中的这一部分通常被称为标准模板库(STL),你可以在条款49和M35初窥其概貌。但现在你只用知道,变量p工作起来就象一个指针,它将allAccounts中的元素从头到尾循环一遍。也就是说,p工作起来就好象它的类型是BankAccount**而列表中的元素都存储在一个数组中。</P> 
<P>上面的循环不能通过编译很令人泄气。的确,allAccounts是被定义为保存BankAccount*s,但要知道,上面的循环中它事实上保存的是SavingsAccount*s,因为SavingsAccount是仅有的可以被实例话的类。愚蠢的编译器!对我们来说这么显然的事情它竟然笨得一无所知。所以你决定告诉它:allAccounts真的包含的是SavingsAccount*s:</P> 
<P>// 可以通过编译的循环,但很糟糕<BR>for (list&lt;BankAccount*&gt;::iterator p = allAccounts.begin();<BR>&nbsp;&nbsp;&nbsp;&nbsp; p != allAccounts.end();<BR>&nbsp;&nbsp;&nbsp;&nbsp; ++p) {</P> 
<P>&nbsp; static_cast&lt;SavingsAccount*&gt;(*p)-&gt;creditInterest();</P> 
<P>}</P> 
<P>一切问题迎刃而解!解决得很清晰,很漂亮,很简明,所做的仅仅是一个简单的转换而已。你知道allAccounts指针保存的是什么类型的指针,迟钝的编译器不知道,所以你通过一个转换来告诉它,还有比这更合理的事吗?</P> 
<P>在此,我要拿圣经的故事做比喻。转换之于C++程序员,就象苹果之于夏娃。</P> 
<P>这种类型的转换 ---- 从一个基类指针到一个派生类指针 ---- 被称为 "向下转换",因为它向下转换了继承的层次结构。在刚看到的例子中,向下转换碰巧可以工作;但正如下面即将看到的,它将给今后的维护人员带来恶梦。</P> 
<P>还是回到银行的话题上来。受到存款帐户业务大获成功的激励,银行决定再推出支票帐户业务。另外,假设支票帐户和存款帐户一样,也要负担利息:</P> 
<P>class CheckingAccount: public BankAccount {<BR>public:<BR>&nbsp; void creditInterest();&nbsp;&nbsp;&nbsp; // 给帐户增加利息</P> 
<P>&nbsp; ...</P> 
<P>};</P> 
<P>不用说,allAccounts现在是一个包含存款和支票两种帐户指针的列表。于是,上面所写的计算利息的循环转瞬间有了大麻烦。</P> 
<P>第一个问题是,虽然新增了一个CheckingAccount,但即使不去修改循环代码,编译还是可以继续通过。因为编译器只是简单地听信于你所告诉它们(通过static_cast)的一切:*p指向的是SavingsAccount*。谁叫你是它的主人呢?这会给今后维护带来第一个恶梦。维护期第二个恶梦在于,你一定想去解决这个问题,所以你会写出这样的代码:</P> 
<P>for (list&lt;BankAccount*&gt;::iterator p = allAccounts.begin();<BR>&nbsp;&nbsp;&nbsp;&nbsp; p != allAccounts.end();<BR>&nbsp;&nbsp;&nbsp;&nbsp; ++p) {</P> 
<P>&nbsp; if (*p 指向一个 SavingsAccount)<BR>&nbsp;&nbsp;&nbsp; static_cast&lt;SavingsAccount*&gt;(*p)-&gt;creditInterest();<BR>&nbsp; else<BR>&nbsp;&nbsp;&nbsp; static_cast&lt;CheckingAccount*&gt;(*p)-&gt;creditInterest();</P> 
<P>}</P> 
<P>任何时候发现自己写出 "如果对象属于类型T1,做某事;但如果属于类型T2,做另外某事" 之类的代码,就要扇自己一个耳光。这不是C++的做法。是的,在C,Pascal,甚至Smalltalk中,它是很合理的做法,但在C++中不是。在C++中,要使用虚函数。</P> 
<P>记得吗?对于一个虚函数,编译器可以根据所使用对象的类型来保证正确的函数调用。所以不要在代码中随处乱扔条件语句或开关语句;让编译器来为你效劳。如下所示:</P> 

⌨️ 快捷键说明

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