48
第6第 第第第第第 第第第第第第第 第第第第第第第第第第第第第第第 第第第 第第第第第第第 第 一统 第第第第第第第第第第第第第第 第第第 第第第第第第“ 。, +” 第第第 第第第 第第第“第”第第第第第第第 第第第第第第第第第第第第第第第第第第“第” 第第第第第第第第第第第第第第第 第第第第第第第 第第第第第第第第 一, 第第第 第第第 第第第 第第第第第第第第第第第第第第第第第第第 体体,。

第 6 章 运算符重载、虚函数与多态性

  • Upload
    etan

  • View
    207

  • Download
    6

Embed Size (px)

DESCRIPTION

第 6 章 运算符重载、虚函数与多态性. 运算符重载是指用户可以重新定义一个系统已有的运算符,以便可以用于新的数据类型的操作。例如,我们可以重载“ +” 运算符,使之在系统已有的“加”运算符的基础上,可以用于字符串的连接和复数数据类型的“加”的操作。. 而虚函数是指在其类中定义一个函数的空壳,在各派生类中定义其函数体的具体内容,从而实现执行同样的代码但运行结果不同。. 6.1 多态性概述. 多态性的英文单词是 polymorphism ,意为多个形态或多种状态,简称多态性。在面向对象中,多态性是指不同的对象收到相同的消息时,产生不同的动作。. - PowerPoint PPT Presentation

Citation preview

Page 1: 第 6 章  运算符重载、虚函数与多态性

第 6 章 运算符重载、虚函数与多态性 运算符重载是指用户可以重新定义一个系统已有的运算符,以便可以用于新的数据类型的操作。例如,我们可以重载“ +” 运算符,使之在系统已有的“加”运算符的基础上,可以用于字符串的连接和复数数据类型的“加”的操作。 而虚函数是指在其类中定义一个函数的空壳,在各派生类中定义其函数体的具体内容,从而实现执行同样的代码但运行结果不同。

Page 2: 第 6 章  运算符重载、虚函数与多态性

6.1 多态性概述 多态性的英文单词是 polymorphism ,意为多个形态或多种状态,简称多态性。在面向对象中,多态性是指不同的对象收到相同的消息时,产生不同的动作。 在 C++ 中,多态性是指一个函数名有多种函数体内容,有函数重载、运算符重载和虚函数方式。其中,函数重载、运算符重载是在编译时就确定调用哪一个函数体内容,称为静态多态性。而虚函数是在运行时才确定哪一个函数体内容,因此称为动态多态性。

Page 3: 第 6 章  运算符重载、虚函数与多态性

6.2 运算符重载

实际上在 C 语言中 , 有许多系统预定义的运算符例如“ +”, 它可以用于整型值 , 也可以用于实型值 , 虽然使用相同的运算符 , 但生成的代码不相同。因此可以说 , 像“ +” 这样的运算符在 C 语言中已被重载了。 C++ 语言扩充了这个功能 , 允许已存在的运算符由用户在不同的上下文中作出不同的解释。

运算符重载的目的是为了使系统已有的运算符能够用于新的数据类型上。

Page 4: 第 6 章  运算符重载、虚函数与多态性

6.2.1 运算符函数的定义 运算符重载是通过定义运算符函数来完成的。在 C++ 中,运算符重载通过一个运算符函数来进行。运算符函数可以被设计为成员函数,也可以被设计为友元函数。

上述定义中“ operator 运算符”就是所谓的运算符函数的名字。

运算符函数的定义格式为: 结果类型名 operator 运算符(操作数表) { ...... // 相对于该类而重新定义的运算符含义 }

Page 5: 第 6 章  运算符重载、虚函数与多态性

运算符一般分为一元和二元运算符,一元运算符需要一个操作数,二元运算符需要二个操作数。操作数表中罗列的就是该运算符所需要的操作数。 运算符函数体对重载的运算符的含义作出新的解释。这里,所解释的含义只限定在重载该运算符的类范围内。当在该类对象的上下文中,该运算符的含义由这个函数体进行解释 ( 或称执行 ), 否则 , 该运算符具有系统原有的含义。因此,当一个运算符被重载时,它所有原先的含义并未失去,只是在一个特定类的范围内重新定义了该运算符的含义。

Page 6: 第 6 章  运算符重载、虚函数与多态性

在进行运算符重载时必须注意以下几个问题 : ① 重载的运算符必须是系统已定义的运算符 , 如不能定义一个这样的运算符函数: int operator$(...);

②C++ 中大多数的运算符是可以重载的,但下列运算符是不能重载的: . 成员运算符 .* 成员指针运算符 :: 作用域运算符 # 预编译运算符 ?: 条件运算符 sizeof 求字节数运算符

下面我们重载运算符 + 和 - ,以便用于复数complex 类的操作 ( 例 cpp6-1) 。

Page 7: 第 6 章  运算符重载、虚函数与多态性

③ 重载这些运算符时,不能改变它们的优先级,不能改变它们的操作数; ④ 运算符函数既可被设计为成员函数,也可被设计为友元函数,但以下四种运算符的运算符函数只能被设计为成员函数: = 赋值运算符 ( ) 函数运算符 [ ] 下标运算符 -> 指向运算符 ⑤ 重载的运算符至少必须作用于一个以上用户定义的数据类型。例如下面的定义是错误的: int operator+(int a,int b) { return(a+b); }

Page 8: 第 6 章  运算符重载、虚函数与多态性

⑥ 除了“ =” 以外,重载的运算符可以被任何派生类所继承。如果每个类都需要一个“ =” 运算符,那么每个类就要明确地定义自己的“ =” 运算符。 由于能够对旧运算符定义新含义,有可能使编出来的程序几乎不可理解。例如,在一个程序中把运算符“ +” 用来表示减运算,而“ *” 用来表示除运算,那么该程序的阅读将是非常的困难。因此,运算符重载主要用来模仿运算符的习惯用法,用于解决新数据类型的类似运算功能,不应该标新立异。

Page 9: 第 6 章  运算符重载、虚函数与多态性

6.2.2 用成员函数与用友元函数重载运算符的区别

重载运算符时,运算符函数可被设计为成员函数,也可被设计为友元函数。这两种方式非常类似,但还有许多差别,关键原因在于成员函数属于某个类,具有 this 指针,而友元函数不属于类范围,没有 this 指针。 以下假定用“ @” 表示任意可重载的运算符。

Page 10: 第 6 章  运算符重载、虚函数与多态性

一元运算符: 对于一元运算符,不论是前缀还是后缀,都需要一个操作数: aa@ 或 @aa

对任意一元运算符 @, 都可有如下两种解释 : aa.operator@( ) (1)或 operator@(aa) (2)

在式 (1) 的解释下,一元运算符函数 operator@ 所需的一个操作数由对象 aa 通过 this 指针隐含地传递。因此,它的参数表为空。这时,运算符函数用类的成员函数来表示。

Page 11: 第 6 章  运算符重载、虚函数与多态性

在式 (2) 的解释下,一元运算符函数 operator@ 所需的一个操作数在参数表中由对象 aa显式地提供,这适合没有 this 指针的情况。这时,运算符函数用类的友元函数来表示。 不管是用成员函数还是友元函数重载运算符,该运算符的使用方法是相同的,都是这样使用的: aa@ 或 @aa这里, aa 是重载运算符 @ 的那个类的对象。

Page 12: 第 6 章  运算符重载、虚函数与多态性

二元运算符: 同样地,对任意的二元运算符 @ : aa@bb 可解释为 aa.operator@(bb) (3)或 operator@(aa,bb) (4)

在式 (3) 的解释下,二元运算符函数 operator@ 所需的一个操作数由对象 aa 通过 this 指针隐含地传递。因此,它只有一个参数 bb 。这时,运算符函数用类的成员函数来表示。

Page 13: 第 6 章  运算符重载、虚函数与多态性

在式 (4) 的解释下,二元运算符函数 operator@ 所需的两个操作数都在参数表中由对象 aa 和 bb显式地提供,这适合没有 this 指针的情况。这时,运算符函数用类的友元函数来表示。 不管是用成员函数还是友元函数重载运算符,该运算符的使用方法是相同的,都是这样使用的: aa@bb

用成员函数重载运算符和使用友元函数重载运算符,它们传递参数的方法不一样,也就导致了它们的实现代码不相同。

Page 14: 第 6 章  运算符重载、虚函数与多态性

下面仔细谈谈它们的差别。 创建一个名为 threed 的简单类,它包含三维空间中一点的 x,y,z 坐标值。重载“ ++” 、“ +” 和“ =” 运算符,以便能进行三维点对象的相加和赋值。分别用成员函数和友元函数来实现,然后再来对比它们的不同。 例 cpp6-2 。 从两个程序的输出结果完全相同可以看出,无论用那种方法重载运算符,使用方法是完全相同的。但两者的声明和定义是有差别的:

Page 15: 第 6 章  运算符重载、虚函数与多态性

(1) 在类的声明中,友元函数除了多一个 friend关键字外 , 最主要的是多了一个操作数 , 如下所示:成员函数 : threed operator+(threed t);友元函数 : friend threed operator+(threed op1, threed op2);

(2) 在运算符函数的类外定义中,成员函数需要加“类名 ::” 来限定其范围: 成员函数: threed threed::operator+(threed t) 友元函数: threed operator+(threed op1, threed op2)

Page 16: 第 6 章  运算符重载、虚函数与多态性

(3) 在函数体内,成员函数有 this 指针,所以x,y,z 可以直接引用,而在友元函数中两个操作数都必须指明其对象: { three_d temp; { three_d temp; temp.x=x+t.x; temp.x=op1.x+op2.x; temp.y=y+t.y; temp.y=op1.y+op2.y; temp.z=z+t.z; temp.z=op1.z+op2.z; return temp; return temp; } }

(4) 当需要把修改后的结果返回给类对象时,用友元函数重载运算符时操作数表必须用引用参数。

Page 17: 第 6 章  运算符重载、虚函数与多态性

例如,上例中的“ ++” 运算符,采用的就是引用参数: three_d operator++(three_d &op1)

(5) 对于大多数情况下,用成员函数重载运算符或用友元函数重载运算符都是可行的,仅仅是实现方法不同而已。但在有些情况下,两者是不能相互替代的。如前所述,不能用友元函数重载“ = 、 ( ) 、 [ ] 、 ->” 这四个运算符;而当二元运算符中的第一个操作数不是对象,只是一般数据时用成员函数重载的运算符会出现问题,而用友元函数重载的运算符不会出现问题。例 cpp6-3 。

Page 18: 第 6 章  运算符重载、虚函数与多态性

在成员函数重载的 + 运算符中,表达式 z+27可被解释为: z.operator+(27)z 是一个复数类的对象,系统由此知道现在用的是复数类中“ +” 的重载版本,所需参数应为复数,于是系统将 27 自动进行类型转换,再与 z 相加。因此表达式能正确地工作。 但是,表达式 27+z 被解释为: 27.operator+(z)这个式子毫无意义。因为 27 不是用户定义的对象,系统不知道用户要重载运算符“ +” ,把它解释为一般的整型值,这样再与后面的 complex 类对象 z相加时就出现错误。

Page 19: 第 6 章  运算符重载、虚函数与多态性

如果用友元函数来重载该运算符就可以避免这种错误。由于友元函数所需的两个操作数都必须在参数表中明确地说明,因此很容易对此情况进行处理。 表达式 27+z 被解释为: operator +(27,z);

由于 z 是一个复数类的对象,系统由此知道现在用的是复数类中“ +” 的重载版本,所需参数应为复数,于是系统将 27 自动进行类型转换,它通过调用构造函数 complex(int a)版本将整型值 27 变为 complex 类 27 ,再与 z 相加。因此表达式能正确地工作。 下面再看一个用友元函数重载的例子 cpp6-4 。

Page 20: 第 6 章  运算符重载、虚函数与多态性

6.2.3 重载 ++ 和 -- 在 C 和 C++ 中,运算符 ++ 和 -- 有两种方式,即前缀方式: ++i; 或 --i;和后缀方式: i++; 或 i--;

早期的 C++ 在重载符 ++ 或 -- 时,不能显式地区分是前缀方式还是后缀方式。但在新的 C++标准中,对此作了一些约定。

Page 21: 第 6 章  运算符重载、虚函数与多态性

对于前缀方式 ++i ,可以用一个一元运算符函数重载为: aa.operator++(); // 成员函数重载或 operator++(X &aa); // 友元函数重载 对于后缀方式 i++ ,可以用一个二元运算符函数重载为: aa.operator++(int); // 成员函数重载或 operator++(X &aa, int); // 友元函数重载

Page 22: 第 6 章  运算符重载、虚函数与多态性

这时,第二个参数 (int) 一般设置为 0 ,例如: i++;等价于 i++(0); 类似地,也可重载为友元函数。 例 cpp6-5 。 重载运算符 -- 也用类似的方法。 6.2.4 重载 [ ] 运算符C++中数组下标运算符的一般使用格式为: 数组名 [表达式 ]

Page 23: 第 6 章  运算符重载、虚函数与多态性

在重载“ [ ]” 时, C++ 也把它看成双目运算符,其操作数为“数组名”和“表达式”,相应的运算符函数为 operator[ ] 。 设 a是类 A的对象,类 A中定义了重载“ []”的 operator函数,则表达式 a[3];可被解释为: a.operator[ ](3);

对下标运算符重载定义只能使用成员函数,其形式如下: 类型 类名:: operator[](形参) { 函数体 }

Page 24: 第 6 章  运算符重载、虚函数与多态性

下面的例子给出重载下标运算符的用法。例 6.5

分析:在这个程序中,重载“ []” 的 operator函数被定义为: char &operator[](int i)

{ return *(str+i);}则 word[n-1]=word[n-1]-32;相当于: word.operator[](n-1)=word.operator[](n-1)-32;它的功能是将小写字母转换成大写字母。

这里,形参只能有一个。

Page 25: 第 6 章  运算符重载、虚函数与多态性

6.2.5 重载运算符 ( ) 函数调用运算符可以带零个或多个参数。下面的例子就是重载函数调用运算符“ ( )” 来实现: f(x,y)= (x2-5)(y+8) 例 6.6(P166)

分析:该程序中,定义了 F类的两个对象 f1和 f2。在程序中出现的表达式 f1(2.7,6.2) 和 f2(3.3,7.8)分别被编译程序解释为: f1.operater( )(2.7,6.2) 和 f2.operater( )(3.3,7.8)

Page 26: 第 6 章  运算符重载、虚函数与多态性

6.3 派生类与基类的转换

6.3.1 派生类对象与基类对象之间的转换

派生类与基类之间的转换主要涉及三个方面:派生类与基类对象之间的转换、派生类与基类对象指针之间的转换、派生类与基类对象成员指针之间的转换。

在继承的关系下,派生类的对象可以直接赋值给其 public 基类的对象,而不须经过任何转换。 如:

Page 27: 第 6 章  运算符重载、虚函数与多态性

class Base { ... };class Derived: public Base { ... };void main(){ Derived devobj; Base basobj=devobj; // 将派生类对象直接赋给 基类的对象 ...}

此项转换只限于当基类为 public 时,对于 private 的基类则不适用:

Page 28: 第 6 章  运算符重载、虚函数与多态性

class PubBase { ... };class PriBase { ... };class Derived: public PubBase, private PriBase { ... };void main( ){ Derived devobj; PubBase basobj=devobj; //正确 PriBase priobj=devobj; // 错误 ...}

Page 29: 第 6 章  运算符重载、虚函数与多态性

C++ 之所以允许这样的赋值法乃是因为每一个派生类对象中皆含有一个 public 基类的对象,使得这样的赋值语句是安全的: Base basobj = devobj;

上述赋值说明对象 basobj永远可以使用到派生类所指定过来的 public 基类部分。相反地,若将一基类的对象赋值给派生类对象,则程序员必须明确使用强制类型转换方式来实现赋值,如: devobj=(Derived )basobj;

Page 30: 第 6 章  运算符重载、虚函数与多态性

6.3.2 派生类对象指针与基类对象指针之间的转换 事实上,不只是派生类的对象可以直接赋值给其 public 对象,派生类对象的引用和指针也可以直接赋值给其指向 public 基类对象的引用和指针变量。 指向基类和派生类的指针是相关的,假设 B_class 是基类, D_class 是从 B_class公有派生出来的派生类,在 C++ 中,任何被说明为指向 B_class 的指针也可以指向 D_class 。

例如: B_class *p,b_obj; D_class d_obj; p=&b_obj; p=&d_obj;

Page 31: 第 6 章  运算符重载、虚函数与多态性

可以用一个指向基类的指针指向其公有派生类的对象。但是相反却不正确,即不能用指向派生类的指针指向一个基类的对象。如果希望用指向基类的指针访问其公有派生类对象的特定成员,必须将基类指针用强制类型转换方式转换为派生类指针。 下面看一个实例 cpp6-6 。 一个指向基类的指针可用来指向从基类公有派生的任何对象,这是 C++ 实现虚函数与多态性的关键途径。

Page 32: 第 6 章  运算符重载、虚函数与多态性

6.4 虚函数与多态性 在前面的介绍中,对于某一个类的普通成员函数的重载,可以通过以下三个方面进行区分: ①若成员函数的参数类型和个数不同,则可以根据参数的特征来区分 , 这是一般的函数重载。如: show(int, char); show(char * , float);

②使用类名和作用域运算符来区分。如: Circle::show(); Message::show();

Page 33: 第 6 章  运算符重载、虚函数与多态性

③ 通过对象来区分。如: mcobj.show();

所有上述这些函数的区分都是在编译时进行的,称为早期匹配或静态联编。 除此之外, C++还提供了一种更为灵活的机制来解决函数匹配问题,它允许 show() 函数的使用与 show() 的实现版本之间的联系推迟到程序运行时进行,称为晚期匹配或动态联编(滞后联编)。 虚函数正是解决这些问题的关键概念。

Page 34: 第 6 章  运算符重载、虚函数与多态性

6.4.1 虚函数的概念 在继承体系中,如果在派生类中要对所继承的成员函数重新定义其功能时,该函数应在基类中被定义为虚函数,即在成员函数定义时在其前面加上关键字 Virtual 。当调用此成员函数时, C++ 系统能自动判别应该调用哪一个类对象的成员函数。 因此,虚函数是一种单界面多实现版本的方法,即函数名、返回类型、参数类型和个数及顺序完全相同,但函数体内容可以完全不同。

Page 35: 第 6 章  运算符重载、虚函数与多态性

前面我们说过,如果在派生类中重新定义了一个与基类中成员函数同名的成员函数,则基类的成员函数将被隐藏起来,但它仍然存在,且可以通过基类名和作用域运算符来使用。

看下面的例子 cpp6-7 。

如果采用指向对象的指针 ( 或引用 ) 来调用,则指向基类的指针永远只能调用属于基类的成员函数,而指向派生类的指针永远只能调用属于派生类自己的成员函数。

Page 36: 第 6 章  运算符重载、虚函数与多态性

从运行结果来看: (1) 指向基类的指针 p ,不管赋给它的是基类对象 bobj 的地址还是派生类对象 fobj 和 sobj 的地址, p->who() 调用的始终是基类中定义的版本。 (2) 指向派生类的指针 fp 和 sp ,它们调用的who() 是各自的派生类内重新定义的 who()版本。 (3) 无论是指向基类的指针还是指向派生类的指针,当用强制类型转换后,所调用的 who() 函数就变成了转换后的类类型的 who()版本。

Page 37: 第 6 章  运算符重载、虚函数与多态性

前面我们说过,一个指向基类的指针可直接用来指向从基类公有派生的任何对象,因此我们能不能用一个指向基类的指针变量,通过赋给它的对象地址的改变,达到同一个调用语句实现不同版本的函数调用?

为了实现这个愿望, C++提出了虚函数的概念,它是在基类中成员函数的定义时在其前面加上 virtual关键字(在派生类中重新定义该函数时不需加关键字 virtual )。

例如上例中, (4) 和 (6) 语句能够调用派生类First_d 和 Second_d 各自的 who()版本。

Page 38: 第 6 章  运算符重载、虚函数与多态性

加上 virtual后,基类成员函数的版本在派生类中就不再存在了(原来是被隐藏起来),因而在派生类就只有一个成员函数的版本了,那就是那个属于自己的重新定义的版本,并且当把派生类对象的地址 ( 或引用 ) 赋给指向基类的指针 ( 或引用 )变量时,用指向基类的指针变量来调用虚函数,则执行的就是派生类自己的函数版本。 为了验证,我们把上面的例子中的 who() 函数定义为虚函数,然后重新运行程序,看看结果怎样?

Page 39: 第 6 章  运算符重载、虚函数与多态性

6.4.2 多态性的实现 从前三行的结果说明,通过虚函数和指向不同对象的基类指针, C++ 系统能自动判别应该调用哪一个类对象的成员函数,即同一个语句“ p->who(); ” ,由于赋给 p 的对象地址不同,使得能够实现调用不同 who() 函数版本的方法。由于所调用函数 who() 的版本是在程序运行时确定的,因此我们称为晚期匹配,也称为运行时的多态性。 由上可见,在继承体系下,用虚函数实现运行时的多态性的关键之处有三(可称为三要素):

Page 40: 第 6 章  运算符重载、虚函数与多态性

②在派生类定义中,对虚函数的重新定义只能修改函数体内容,而函数名、返回类型、参数个数、参数类型及参数顺序必须与基类的定义完全相同。若是返回类型不同,则 C++认为是错误的;若是参数不同,则 C++认为是一般的函数重载。 ③ 必须用指向基类的指针 ( 或引用 )访问虚函数。其他方式都不能实现运行时的多态性。

① 在基类定义中,必须把成员函数定义为虚函数,即在正常函数定义之前加关键字“ virtual” ;

Page 41: 第 6 章  运算符重载、虚函数与多态性

注意,在虚函数下,仍然可以用“对象名 . ”或“类名 ::” 来调用该虚函数,但此时虚特性丢失。 下述例子 cpp6-8 说明了虚函数的三要素及虚特性问题。 我们说虚函数与多态性是一种界面多种实现,是 C++ 面向对象程序设计的关键 , 它的实现过程是:在基类中用虚函数提供一个界面即定义一个函数原型,这是该类公有派生的对象都具有的共同界面 , 在派生类中重新定义该虚函数的函数体内容 ( 实现版本 ), 然后通过指向基类的指针 ( 或引用 ) 指向不同的派生类对象 , 达到访问虚函数的不同实现版本。 下面再看一个虚函数的实例 cpp6-9 。

Page 42: 第 6 章  运算符重载、虚函数与多态性

虚函数必须是类的成员函数,不能将虚函数说明为全局 ( 非成员的 ) 函数 , 也不能说明为静态成员函数。不能把友元函数说明为虚函数,但虚函数可以是另一个类的友元 ( 友元类 ) 。析构函数可以是虚函数 , 但构造函数不能为虚函数。另外 , 当在构造函数和析构函数中调用虚函数时 , 虚函数的虚特性将丢失 , 即不能实现动态联编。 一旦一个函数被说明为虚函数,不管经历了多少派生类层次 , 都将保持其虚特性。当一个派生类没有重新定义虚函数时,则使用其直接基类的虚函数版本。

Page 43: 第 6 章  运算符重载、虚函数与多态性

6.5 纯虚函数与抽象类6.5.1 纯虚函数 有时我们设计一个基类只是为了作为其他派生类的基类,提供各派生类一个公共的界面,并不需要为这些虚函数提供有实际意义的函数内容,真正的内容是在各派生类中定义的,为此, C++引入了纯虚函数的概念。 纯虚函数是一个在基类中说明的虚函数,它在该基类中设定为 0 ,要求任何派生类都必须定义自己的函数体内容。纯虚函数的原型为: virtual 返回类型 函数名 (参数表 )=0 ;

Page 44: 第 6 章  运算符重载、虚函数与多态性

6.5.2 抽象类 具有一个以上纯虚函数的类称为抽象类。抽象类只能作为其他派生类的基类,不能建立自己的对象。抽象类也不能作为参数类型、函数返回值类型或显式转换的类型。但可以声明抽象类的指针和引用。 下面看一下实例 cpp6-10 。 将一虚函数说明为纯虚函数,就要求派生类都重新定义该函数的实现版本。如果不对其定义 ,则在派生类中该虚函数仍为纯虚函数,因此该派生类也就仍是抽象类。

Page 45: 第 6 章  运算符重载、虚函数与多态性

例如: class ab_circle: public shape { private: int radius; public: void rotate(int){ } };

由于 shape::draw() 是一个纯虚函数,缺省的 ab_circle::draw() 也是一个纯虚函数,所以 ab_circle仍为抽象类。 要使 ab_circle 类为非抽象的,必须作如下说明:

Page 46: 第 6 章  运算符重载、虚函数与多态性

class ab_circle: public shape

{ private:

int radius;

public:

void rotate(int);

void draw();

};

并提供 ab_circle::draw() 和 ab_circle::rotate(int)的定义。

Page 47: 第 6 章  运算符重载、虚函数与多态性

作业: 1 用虚函数和多态性改写第 5 章的两个作业。 2 设计一个时间类 Time: 有三个成员变量 hour 、 minute 、 second, 定义一个 Show 成员函数能按 “hh:mm:ss” 格式输出时间 , 定义一个没有参数的 构造函数和一个有三个参数的构造函数 , 重载 + 、 - 、 ++ 和 = 运算符。

Page 48: 第 6 章  运算符重载、虚函数与多态性

实例:应用派生类和虚函数设计一个面向对象程序: (1) 定义一个抽象基类 Circle, 它有一个实型数据成员 ra

dius, 代表圆的半径;定义并实现一个构造函数 Circle (double r1) ,定义一个求圆面积的虚函数 double area() 。

(2) 从 Circle 类公有派生一个球体类 Sphere ,定义并实现一个有一个参数的构造函数 Sphere (double r2) ,实现其求表面积函数 area() 的具体代码(球体的表面积 =4πR2 )。

(3) 从 Circle 类公有派生一个圆柱体类 Cylinder ,定义一个代表高度的实型数据成员 height ,定义并实现一个有二个参数的构造函数 Cylinder (double r1, double h) ,实现其求表面积函数 area() 的具体代码(圆柱体的表面积 =2πRh )。

(4) 在 main 函数中,定义一个 Circle 的指针 p 、一个 Circle 的对象、一个 Sphere 对象和一个 Cylinder 对象,用 p 指针实现虚函数的多态性,计算并打印出圆、球体和圆柱体的面积。