第10章多态性与虚函数
【内容提要】
多态性的概念;
函数和运算符的重载;
虚函数和抽象类。
【重点与难点】
10.1 多态性的概念
在面向对象的概念中,多态性是指不同对象接收到相同消息时,根据对象类的不同产生不同的动作。
由静态联编支持的多态性称为编译时的多态性或静态多态性,也就是说,确定同名操作的具体操作对象的过程是在编译过程中完成的。C++用函数重载和运算符重载来实现编译时的多态性。
由动态联编支持的多态性称为运行时的多态性活动太多态性,也就是说,确定同名操作的具体操作对象的过程是在运行过程中完成的。C++用继承和虚函数来实现运行时的多态性。
10.2 函数和运算符的重载
10.2.1 函数重载
面向对象程序设计中,函数的重载表现为两种情况:第一种是参数个数或类型有所差别的重载,第二种是函数的参数完全相同但属于不同的类。
10.2.2 运算符重载
C++预定义的运算符只是对基本数据类型进行操作,而对于自定义的数据类型比如类,却没有类似的操作。为了实现对自定义类型的操作,就必须自己编写程序来说明某个运算符作用在这些数据类型上时,应该完成怎样的操作,这就要引入运算符重载的概念。
运算符的重载形式有两种,一种是重载为类的成员函数,一种是重载为类的友元函数。
成员运算符函数的定义:
在类内声明的一般形式为:
<返回类型> operator<运算符>(参数表);
在类外定义的一般形式为:
<返回类型> <类名∷> operator<运算符>(参数表)
{
函数体
}
其中,operator是定义运算符重载函数的关键字;运算符是要重载的运算符的名称;参数表给出重载运算符所需要的参数和类型。
将重载的运算符函数定义为类的友元函数,称为友元运算符函数。友元运算符函数不
友员运算符函数的定义:
在类内声明的一般形式为:
friend<返回类型> operator<运算符>(参数表);
在类外定义的一般形式为:
<返回类型> operator<运算符>(参数表)
{
函数体
}
其中,friend是声明友元函数的关键字,operator是定义运算符重载函数的关键字;运
算符是要重载的运算符的名称;参数表给出重载运算符所需要的参数和类型。
几种典型运算符的重载
①加法运算符“+”的重载
②“++”和“--”的重载
③赋值运算符“=”的重载
④函数调用运算符“()”的重载
⑤下标运算符“[ ]”的重载
10.3 虚函数和抽象类
虚函数是重载的另一种形式,实现的是动态的重载,即函数调用与函数体之间的联系是在运行时才建立,也就是动态联编。
10.3.1 虚函数的定义和使用
虚函数的定义是在基类中进行的,即把基类中需要定义为虚函数的成员函数声明为virtual。当基类中的某个成员函数被声明为虚函数后,它就可以在派生类中被重新定义。在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数和类型、参数的顺序都必须与基类中的原型完全一致。
虚函数定义的一般形式为:
virtual<函数类型><函数名>(参数表)
{
函数体
}
使用虚函数时应注意如下问题:
①虚函数的声明只能出现在类声明的函数原型的声明中,不能出现在函数体实现的时候,
而且,基类中只有保护成员或公有成员才能被声明为虚函数。
②在派生类中重新定义虚函数时,关键字virtual可以写也可不写,但在容易引起混乱时,
应写上该关键字。
③动态联编只能通过成员函数来调用或通过指针、引用来访问虚函数,如果用对象名的形
式来访问虚函数,将采用静态联编。
④虚函数必须是所在类的成员函数,不能是友元函数或静态成员函数。但可以在另一个类
中被声明为友元函数。
⑤构造函数不能声明为虚函数,析构函数可以声明为虚函数。
⑥由于内联函数不能在运行中动态确定其外治,所以它不能声明为虚函数。
10.3.2 纯虚函数和抽象类
抽象类是一种特殊的类,它为一族类提供统一的操作界面,建立抽象类就是为了通过它多态地使用其中的成员函数。抽象类是带有纯虚函数的类。
一个抽象类至少带有一个纯虚函数。纯虚函数是在一个基类中说明的虚函数,它在该基类中没有具体的操作内容,要求各派生类在重新定义时根据自己的需要定义实际的操作内容。纯虚函数的一般定义形式为:
virtual<函数类型><函数名>(参数表)=0;
纯虚函数与普通虚函数的定义的不同在于书写形式上加了“=0”,说明在基类中不用定义该函数的函数体,它的函数体由派生类定义。
如果一个类中至少有一个纯虚函数,这个类就成为抽象类。它的主要作用是为一个族类提供统一的公共接口,以有效地发挥多态的特性。使用时应注意以下问题:
①抽象类只能用作其它类的基类,不能建立抽象类的对象。因为它的纯虚函数没有定义功
能。
②抽象类不能用作参数类型、函数的返回类型或显式转换的类型。
③可以声明抽象类的指针和引用,通过它们,可以指向并访问派生类对象,从而访问派生
类的成员。
④若抽象类的派生类中没有给出所有纯虚函数的函数体,这个派生类仍是一个抽象类。若
抽象类的派生类中给出了所有纯虚函数的函数体,这个派生类不再是一个抽象类,可以声明自己的对象。
【典型例题】
例题1.下面关于虚函数和函数重载的叙述不正确的是()。
(a)虚函数不是类的成员函数
(b)虚函数实现了C++的多态性
(c)函数重载允许非成员函数,而虚函数则不行
(d)函数重载的调用根据参数的个数、序列来确定,而虚函数依据对象确定
解答:
函数重载和虚函数是C++中实现多态性的两种手段,但是它们的实现机制是不一样的;函数重载依据调用时的参数进行区分,而虚函数则根据对象实际的指向确定调用的版本。答案为:a。
例题2.()是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生类都必须定义自己的版本。
(a)纯虚函数(b)虚析构函数(c)虚构造函数(d)静态成员函数
解答:
抽象类中的纯虚函数没有具体的定义,需要在抽象类的派生类中定义。因此,纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生类都必须定义自己的版本。答案为:a
例题3.实现运行时的多态性要使用()。
(a)构造函数(b)析构函数(c)重载函数(d)虚函数
解答:
动态联编要在程序运行时才能确定调用哪个函数。虚函数是实现动态联编的必要条件之一,没有虚函数一定不能实现动态联编。答案为:d。
例题4.关于虚函数的描述中,()是正确的。
(a)派生类的虚函数与基类的虚函数具有不同的参数个数和类型
(b)基类中说明勒虚函数后,派生类中其对应的函数一定要说明为虚函数
(c)虚函数是一个成员函数
(d)虚函数是一个static类型的成员函数
解答:
为实现某种功能而假设的函数称为虚函数。虚函数是用关键字virtual进行说明。虚函数是动态联编的基础。虚函数只能是类中的一个成员函数,但不能是静态成员;派生类的虚函数与基类的虚函数具有相同的参数个数和类型,当派生类的虚函数与基类中的对应的虚函数的参数不同时,派生类的虚函数将丢失虚特性,变为重载函数;对于基类中的虚函数,在派生类中自然是虚函数,可不必说明。答案为:c。
例题5.下面的程序中,有错误的语句是__________。
class A //①
{
public: //②
A()
{
func(); //③
}
virtual void func()=0; //④
};
解答:
在成员函数内可以调用纯虚函数,但在构造函数或析构函数内调用一个纯虚函数将导致程序运行错误,因为没有为纯虚函数定义代码。答案为:③
例题6.运行下列程序的结果为__________________。
#include
class base
public:
void display1(){cout<<"base::display1()"< virtual void display2(){cout<<"base::display2()"< }; class derived:public base { public: void display1(){cout<<"derived::display1()"< void display2(){cout<<"derived::display2()"< }; void main() { base * pbase; derived d; pbase=&d; pbase->display1 (); pbase->display2(); } 解答: 本题主要考查有关多态性的相关知识。在基类base中,定义了一个函数display1()和虚函数display2();在派生类derived中,重写了函数display1(),而且重新定义的虚函数display2()。由于基类指针pbase指向的是派生类的一个对象,因而会调用派生类的display2()版本,但是对于一般的成员函数display1(),仍然遵循一般的调用规则,只调用基类的display1()版本。本题答案为: base::display1() derived::display2() 例题7.下面的程序的输出结果为an animal a person an animal a person,请将程序补充完整。 #include class animal{ public: ______①_____ void speak(){cout<<"An animal"<<" ";} }; class person:public animal{ public: void speak(){cout<<"a person"<<" ";} }; void main() { animal a,_______②_______; person p; a.speak(); p.speak(); pa=&a; pa->speak(); ________③_______; pa->speak(); } 解答: 本题主要考查对多态性的理解与应用。本题通过虚函数实现多态性,所以在基类中应定义虚函数;为了实现多态性,必须定义基类的指针,然后将它指向各个派生类的对象。本题答案为:①virtual、②*pa、③pa=&p 【习题】 一、选择题 1.在C++中,用于实现运行时多态性的是()。 A)内联函数 B)重载函数 C)模板函数 D)虚函数 2.如果一个类至少有一个纯虚函数,那么就称该类为()。 (a)抽象类(b)派生类(c)虚基类(d)以上都不对 3.为了区分一元运算符的前缀和后缀运算,在后缀运算符进行重载时,额外添加一个参 数,其类型是()。 (a)void (b)char (c)int (d)float 4.下面关于运算符重载的说法中,错误的是()。 (a)可以对C++所有运算符进行重载 (b)运算符重载保持固有的结合性和优先级顺序 (c)运算符重载不能改变操作数的个数 (d)在运算符函数中,不能使用缺省的参数值 5.下列关于抽象类的说明中不正确的是()。 (a)含有纯虚函数的类称为抽象类 (b)抽象类不能被实例化,但可声明抽象类的指针变量 (c)抽象类的派生类可以实例化 (d)纯虚函数可以被继承 6.运行下列程序的输出结果为()。 #include class base { public: void fun1(){cout<<"base"< virtual void fun2(){cout<<"base"< }; class derived:public base { public: void fun1(){cout<<"derived"< void fun2(){cout<<"derived"< }; void f(base &b){b.fun1();b.fun2();} int main() { derived obj; f(obj); return 0; } (a)base (b)base (c)derived (d)derived Base derived base derived 7.运行下列程序结果为()。 #include class complex { double re,im; public: complex(double r,double i):re(r),im(i){} double real() const{return re;} double image() const{return im;} complex& operator+=(complex a) { re+=a.re ; im+=a.im ; return *this; } }; ostream &operator<<(ostream&s,const complex& z) { return s<<'('< } int main() { complex x(1,-2),y(2,3); cout<<(x+=y)< return 0; } (a) (1,-2) (b) (2,3) (c) (3,5) (d) (3,1) 二、填空题 1.多数运算符既能作为类的成员函数重载,也能作为类的非成员函数重载,但[ ]运算符 只能作为类的____________函数重载。 2.下列程序的输出结果为2,请将程序补充完整。 #include using namespace std; class Base { public: _____________ void fun( ){ cout<<1; } }; class Derived:public Base { public: void fun( ) { cout<<2; } }; int main( ) { Base *p= new Derived; p->fun( ); delete p; return 0; } 3.运行下列程序结果为_________________。 #include class one { public: virtual void f(){cout<<"1";} }; class two:public one { public: two(){cout<<"2";} }; class three:public two { public: virtual void f(){two::f();cout<<"3"; } }; int main() { one aa,*p; two bb; three cc; p=&cc; p->f(); return 0; } 4.运行下列程序结果为_____________。 #include using namespace std; class A{ public: virtual void funcl(){cout<<"A1";} void func2(){cout<<"A2";} }; class B: public A{ public: void func1(){cout<<"B1";} void func2(){cout<<"B2";} }; int main(){ A *p=new B; p->funcl(); p->func2(); return 0; } 5.下面的程序通过重载运算符“+”实现了两个一维数组对应元素的相加。请将程序补充 完整。 #include class Arr { int x[20]; public: Arr(){for(int i=0;i<20;i++) x[i]=0;} Arr(int *p) {for(int i=0;i<20;i++)x[i]=*p++;} Arr operator+(Arr a) { Arr t; for(int i=0;i<20;i++) t.x[i]=________ ①___________; return _________②________; } Arr operator+=(Arr a) { for(int i=0;i<20;i++)x[i]=______③_______; return ______④________; } void show() { for(int i=0;i<20;i++)cout< cout< } }; void main() { int array[20]; for(int i=0;i<20;i++) array[i]=i; Arr a1(array),a2(array),a3; a3=a1+a2;a3.show (); a1+=a3;a1.show(); } 6.运行下列程序,分别输入”tom”、”m”、”23”、”321456”输出结果为___________________。#include #include class Employee { public: Employee(void) {}; Employee(char *name, char sex, int age, char *phone) { strcpy(Employee::name, name); Employee::sex = sex; Employee::age = age; strcpy(Employee::phone, phone); }; friend ostream &operator<<(ostream &cout, Employee emp); friend istream &operator>>(istream &stream, Employee &emp); private: char name[256]; char phone[64]; int age; char sex; }; ostream &operator<<(ostream &cout, Employee emp) { cout << "Name: " << https://www.wendangku.net/doc/4b7815113.html, << ";Sex: " << emp.sex; cout << ";Age: " << emp.age << ";Phone: " << emp.phone << endl; return cout; } istream &operator>>(istream &stream, Employee &emp) { cout << "Enter Name: "; stream >> https://www.wendangku.net/doc/4b7815113.html,; cout << "Enter Sex: "; stream >> emp.sex; cout << "Enter Age: "; stream >> emp.age; cout << "Enter Phone: "; stream >> emp.phone; return stream; } void main(void) { Employee worker; cin >> worker; cout << worker ; } 7.运行下列程序结果为_______________________。 #include class sample { public: int i; sample *operator->(void) {return this;} }; void main(void) { sample obj; obj->i = 10; cout << obj.i << " " << obj->i; } 8.运行下列程序结果为_________________________。 #include #include class Base { public: virtual int add(int a, int b) { return(a + b); }; virtual int sub(int a, int b) { return(a - b); }; virtual int mult(int a, int b) { return(a * b); }; }; class ShowMath : public Base { virtual int mult(int a, int b) { cout << a * b << endl; return(a * b); }; }; class PositiveSubt : public Base { virtual int sub(int a, int b) { return(abs(a - b)); }; }; void main(void) { Base *poly = new ShowMath; cout << poly->add(562, 531) << ' ' << poly->sub(1500, 407) <<' '; poly->mult(1093, 1); poly = new PositiveSubt; cout << poly->add(892, 201) << ' ' << poly->sub(0, 1093) << ' '; cout << poly->mult(1, 1093); } 三、编程题 1.下面的shape类是一个表示形状的抽象类,area()为求图形面积的函数,total()是一个求不同形状的图形面积总和的函数。请从shape类派生三角形类(triangle)和矩形类(rectangle),并给出具体的求面积函数。 class shape { public: virtual float area()=0; }; float total(shape *s[],int n) { float sum=0.0;