📄 chap02.html
字号:
<P ALIGN="JUSTIFY">之所以举上面的例子是为了将程序空间和现实生活空间来进行对比,结果说明一点,类的继承使得我们可以以一种自然的方式来模拟生活空间中的对象的层次结构,也就是说,我们可以以一种符合正常思维逻辑的自然的方式来思考和组织应用程序的结构,然后,可以将这个结构几乎不作修改或者只需作少量的修改地用面向对象的编程来表达,从而大大的缩短了软件系统的开发周期。</P>
<P ALIGN="JUSTIFY"></P>
<P ALIGN="JUSTIFY">下面我们举一个现实编程中的例子,考虑</FONT><FONT SIZE=3>MFC (Microsoft Foundation Class Library</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,</FONT><FONT SIZE=3>Microsoft</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>基础类库</FONT><FONT SIZE=3>)</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中的</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类,它封装了</FONT><FONT SIZE=3>Windows</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中的编辑框控件,图</FONT><FONT SIZE=3>2.2</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>显示了</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的继承结构。</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P ALIGN="CENTER"><IMG SRC="Image247.gif" tppabs="http://166.111.167.223/computer/cai/visual_c++_5.0_programming/Image247.gif" WIDTH=196 HEIGHT=159></P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P ALIGN="CENTER">图</FONT><FONT SIZE=1>2.2 </FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1>类</FONT><FONT SIZE=1>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1>在</FONT><FONT SIZE=1>MFC</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1>中的继承层次</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">在图</FONT><FONT SIZE=3>2.2</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中,类</FONT><FONT SIZE=3>CObject</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>是所有的</FONT><FONT SIZE=3>MFC</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类的根</FONT><FONT SIZE=3>(</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>根是一个术语,它指在继承层次中处于最顶层的类,根是所有继承层次中的类的最终基类</FONT><FONT SIZE=3>)</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,在类</FONT><FONT SIZE=3>CObject</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中提供了功能有:串行化支持、运行库信息、对象诊断输出以及与集合类的兼容等。类</FONT><FONT SIZE=3>CCmdTarget</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>从类</FONT><FONT SIZE=3>CObject</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>直接派生,它是</FONT><FONT SIZE=3>Microsoft</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>基础类库的消息映射结构的基类,消息映射将命令和消息传递给所编写的处理成员函数,这里,命令指来自菜单项、命令按钮和加速键的消息。类</FONT><FONT SIZE=3>CWnd</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>提供了</FONT><FONT SIZE=3>MFC</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中所有窗口类的基本功能性,它封装了</FONT><FONT SIZE=3>Windows</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中的窗口句柄</FONT><FONT SIZE=3>hWnd</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>。类</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>从</FONT><FONT SIZE=3>CWnd</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>直接派生,它提供了对</FONT><FONT SIZE=3>Windows</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>编辑控件的特定支持。</P>
<P ALIGN="JUSTIFY">我们看到,类</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>本身仅提供了特定于编辑控件的</FONT><FONT SIZE=3>38</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>个成员函数,但是,你可以通过类</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>进行调用的成员函数却多达</FONT><FONT SIZE=3>300</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>多个,事实上,这些成员函数中的绝大部分由其基类所提供,其中</FONT><FONT SIZE=3>CWnd</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>就为</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>提供了多达</FONT><FONT SIZE=3>304</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>个成员函数,由于</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类继承了其基类的数据和方法,因此,可以通过</FONT><FONT SIZE=3>CEdit</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类调用</FONT><FONT SIZE=3>CWnd</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>类中提供的方法来实现对标准</FONT><FONT SIZE=3>Windows</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>窗口的操作。</P>
<P ALIGN="JUSTIFY"></P>
<P ALIGN="JUSTIFY">继承机制所带来的最大优势在于使软件系统非常的易于扩充,程序员不仅可以直接的使用各种已有的类,还可以从这些类方便的派生出新的类,新的类继承了基类所包括的所有接口和功能,因此只需要定义和实现与基类所提供的功能中不同的那一部分,这大大的降低了软件开发的复杂性和费用,因此面向对象的编程方式非常之适合于进行大型软件系统的开发。</P>
<P ALIGN="JUSTIFY">降低软件开发的复杂性的意义不仅在于它可以有效的降低软件开发的费用,而且还使得在软件系统中出错的可能性大为减少。由于类有着清晰的继承层次,因此,我们可以很快的定义出错的代码所处的位置,因此能够很快的修正程序中出现的问题。</P>
<P ALIGN="JUSTIFY">继承机制还使得我们可以将与现实生活空间相一致的思维方式应用于程序空间,即我们可以在程序设计时使用直观的思维方式设计程序中所使用的对象的层次结构,然后,直接将此结构映射到面向对象的程序空间,而不需要做任何修改或仅需要作少量修改就可以使用面向对象的程序设计方法来实现该结构。这时,编写程序的过程更类似于“搭积木”,我们可以从很多途径来获得到程序所需使用的各种对象,然后,将这些对象以一定的层次结构组合起来,从而实现程序的逻辑结构。</P>
</FONT><FONT FACE="Arial"><P>2.1.4 </FONT><FONT FACE="黑体" LANG="ZH-CN">多态和虚函数</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">在讲述多态之前我们先来看一个问题。仍以前面的图</FONT><FONT SIZE=3>2.2</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>为例,假定我们已经定义了一个指向哺乳动物类的实例对象的指针,如下所示:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>CMammal *pMammal</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">然后我们定义了一个人类的实例对象和一个狒狒类的实例对象,如下所示:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>CHuman Human;</P>
<P>CBaboon Baboon;</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">然后,我们可以将指针</FONT><FONT SIZE=3>pMammal</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>指向</FONT><FONT SIZE=3>Human</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象,在</FONT><FONT SIZE=3>C++</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>语言中是可以这样做的:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>pMammal=&Human;</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">也可以将指针指向</FONT><FONT SIZE=3>Human</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>对象:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>pMammal=&Baboon;</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">考查上面的两种情况,我们假定在哺乳动物类、人类和狒狒类中都定义了一个</FONT><FONT SIZE=3>Eat (</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>吃</FONT><FONT SIZE=3>)</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法,很显然,当我们使用下面的代码来调用人类对象和狒狒类对象的</FONT><FONT SIZE=3>Eat</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法时不会遇到什么问题:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>Human.Eat();</P>
<P>Baboon.Eat();</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">但是,现在来考虑下面的代码:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>pMammal->Eat();</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">当</FONT><FONT SIZE=3>pMammal</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>指向不同的对象时,上面的代码将发生什么样的结果。很显然,当</FONT><FONT SIZE=3>pMammal</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>指向一个哺乳动物类的实例对象时,上面的代码将调用哺乳动物类的</FONT><FONT SIZE=3>Eat</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法。但是,当</FONT><FONT SIZE=3>pMammal</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>指向一个人类的实例对象时,上面的代码是调用人类的</FONT><FONT SIZE=3>Eat</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法呢,还是仍然调用哺乳动物类的</FONT><FONT SIZE=3>Eat</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法?我们期望的是前面一种情况,这就是类和对象的多态性。我们期望,当</FONT><FONT SIZE=3>pMammal</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>指向不同的实例对象时,编译器将根据实例对象的类型调用正确的</FONT><FONT SIZE=3>Eat</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法。在</FONT><FONT SIZE=3>C++</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中,类的多态性是通过虚函数来实现的。就上面的例子来说,我们将</FONT><FONT SIZE=3>Eat</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法定义为一个公有的虚函数,这样,当</FONT><FONT SIZE=3>pMammal</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>指针指向一个人类的实例对象时,编译器调用的就是人类的</FONT><FONT SIZE=3>Eat</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>方法,当</FONT><FONT SIZE=3>pMammal</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>指针指向一个狒狒类的实例对象时,编译器调用的就是狒狒类的实例对象,从而实现了运行时的多态。</P>
<P ALIGN="JUSTIFY"></P>
<P ALIGN="JUSTIFY">在</FONT><FONT SIZE=3>C++</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中,多态定义为不同函数的同一接口。从这个定义出发,函数和操作符的重载也属于多态。</P>
<P ALIGN="JUSTIFY">考虑下面定义的两个函数:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>int print(char*);</P>
<P>int print(int);</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">上面的两个不同函数使用同样的函数名,在</FONT><FONT SIZE=3>C++</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中,这称为函数的重载。这时,若将一个字符指针传递给</FONT><FONT SIZE=3>print</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数,如下所示:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>char *sz="Hello World!";</P>
<P>print(sz);</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">这时,编译器调用的是</FONT><FONT SIZE=3>int print(char*)</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,如果将一个整型变量传递给</FONT><FONT SIZE=3>print</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数,如下所示:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>int i=0;</P>
<P>print(i);</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">则编译器调用的是</FONT><FONT SIZE=3>int print(int)</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>。这种根据所传递的参数的不同而调用不同函数的情形也称作多态。</P>
<P ALIGN="JUSTIFY"></P>
<P ALIGN="JUSTIFY">下面我们来看运算符重载的例子,在下面的过程中,我们为矩阵类重载了运算符“</FONT><FONT SIZE=3>+</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>”:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>CMatrix operator +(CMatrix, CMatix);</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">然后使用下面的代码:</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>int a=1, b=1, c;</P>
<P>CMatrix A(3, 3, 0), B(3, 3, 1), C(3, 3);</P>
<P>c=a+b;</P>
<P>C=A+B;</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">在上面的例子中,我们定义了三个整型变量和三个类</FONT><FONT SIZE=3>CMatrix</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的实例变量,</FONT><FONT SIZE=3>A</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>、</FONT><FONT SIZE=3>B</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>和</FONT><FONT SIZE=3>C</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>均为</FONT><FONT SIZE=3>3<FONT FACE="Symbol">´</FONT>
3</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的矩阵,其中</FONT><FONT SIZE=3>A</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>中全部元素均置为</FONT><FONT SIZE=3>0</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>,</FONT><FONT SIZE=3>B</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的全部元素均置为</FONT><FONT SIZE=3>1</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>。然后将“</FONT><FONT SIZE=3>+</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>”运算符作用来整型变量和类</FONT><FONT SIZE=3>CMatirx</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>的实例变量。这时,编译器将表达式</FONT><FONT SIZE=3>c=a+b</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>翻译为</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>c=operator +(a, b);</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">而将表达式</FONT><FONT SIZE=3>C=A+B</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>翻译为</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=1><P>C=operator +(A, B)</P>
</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY">然后,根据传递参数的不同,调用不同的</FONT><FONT SIZE=3>operator +</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>函数。</P>
<P ALIGN="JUSTIFY"></P>
<P ALIGN="JUSTIFY">与类和对象的多态不同,对于函数和运算符的多态,是在编译过程中完成的,因此我们将它们称为编译时多态,与此相对,将类和对象的多态称为运行时多态。</P>
<P ALIGN="JUSTIFY">封装性、继承性和多态性是面向对象编程的三大特征,则开始的时候,你也许对它们还没有非常清晰的概念,但这没有什么关系,当你使用了一段时间的</FONT><FONT SIZE=3>C++</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>语言,然后再回过头来看这些概念时,你就会发现对它们有了更深入的认识和了解。事实上,许多的</FONT><FONT SIZE=3>C++</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>程序正是在使用</FONT><FONT SIZE=3>C++</FONT><FONT FACE="宋体" LANG="ZH-CN" SIZE=3>语言进行编程已有相当一段时间时才对这三个概念有了正确而清晰的认识的。</P>
<UL>
</FONT><FONT FACE="黑体" LANG="ZH-CN" SIZE=3><P ALIGN="JUSTIFY"><LI>注意:</LI></P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -