多态性与封装性、继承性构成了面向对象程序设计的三大特征。
程序的运行过程:编译(生成目标文件)、链接(生成可执行文件)、运行。
一、多态的概述
多态是指同样的消息被不同类型的对象接收时导致完全不同的行为。
所谓消息是指对类的成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数。运算符的使用,就是多态的一种体现。
从实现的角度来看,多态可以划分为两类:编译时的多态和运行时的多态。前者是在编译的过程中确定了同名操作的具体操作对象,而后者则是在程序运行过程中才动态地确定操作所针对的具体对象。这种确定操作的具体对象的过程就是联编(binding),也称为绑定。
联编可以在编译和连接时进行,称为静态联编。在编译、连接过程中,系统就可以根据类型匹配等特征确定程序中操作调用与执行该操作的代码的关系,即确定了某一个同名标识到底是要调用哪一段程序代码,函数的重载、函数模板的实例化均属于静态联编。
联编也可以在运行时进行,称为动态联编。在编译、连接过程中无法解决的联编问题,要等到程序开始运行之后再来确定。
二、静多态与动多态
(1)静多态的演示
演示静态多态性 *
********************************************/
#include
using namespacestd;
class Point
{
private:
int X,Y;
public:
Point(int X=0,intY=0)
{
this->X=X,this->Y=Y;
}
double area() //求面积
{
return0.0;
}
};
const doublePI=3.14159;
class Circle :publicPoint
{
private:
double radius; //半径
public:
Circle(int X, int Y, doubleR):Point(X,Y)
{
radius=R;
}
double area() //求面积
{
returnPI*radius*radius;
}
};
void main()
{
PointP1(10,10);
cout<<"P1.area()="<<P1.area()<<endl;
CircleC1(10,10,20);
cout<<"C1.area()="<<C1.area()<<endl;
Point*Pp;
Pp=&C1;
cout<<"Pp->area()="<<Pp->area()<<endl;
Point&Rp=C1;
cout<<"Rp.area()="<<Rp.area()<<endl;
}
运行结果:
P1.area()=0 C1.area()=1256.64 Pp->area()=0 Rp.area()=0
程序解释:
p1 c1就不用解释了。
对于Pp和Rp可做以下解释:
依照类的兼容性,用一个point型的指针指向了Circle类的对象C1,下一行通过Pp->area()调用area(), 那么究竟调用的是哪个area(),C++此时实行静态联编,根据Pp的类型为Point型,将从Point类中继承来的area()绑定给Pp,因此,此时调用的是Point派生的area(), 显示的结果为0。
同样,由引用Rp调用area()时,也进行静态联编,调用的是Point派生的area(),显示的结果为0。
显然,静态联编盲目根据指针和引用的类型而不是根据实际指向的目标确定调用的函数,导致了错误。
(2)动多态演示
动态联编则在程序运行的过程中,根据指针与引用实际指向的目标调用对应的函数,也就是在程序运行时才决定如何动作。虚函数(virtualfunction)允许函数调用与函数体之间的联系在运行时才建立,是实现动态联编的基础。
虚函数:
虚函数的一般语法如下:
virtual 函数类型 函数表(形参表)
{
函数体;
}
其中:virtual关键字说明该成员函数为虚函数。在定义虚函数时要注意:
(1)虚函数不能是静态成员函数,也不能是友元函数。因为静态成员函数和友元函数不属于某个对象。
(2)内联函数是不能在运行中动态确定其位置的,即使虚函数在类的内部定义,编译时,仍将其看作非内联的。
(3)只有类的成员函数才能说明为虚函数,虚函数的声明只能出现在类的定义中。因为虚函数仅适用于有继承关系的类对象,普通函数不能说明为虚函数。
(4)构造函数不能是虚函数,析构函数可以是虚函数,而且通常声明为虚函数。
在正常情况下,对虚函数的访问与其它成员函数完全一样。只有通过指向基类的指针或引用来调用虚函数时才体现虚函数与一般函数的不同。
使用虚函数是实现动态联编的基础。要实现动态联编,概括起来需要满足三个条件:
(1) 应满足类型兼容规则。
(2) 在基类中定义虚函数,并且在派生类中要重新定义虚函数。
(3)要由成员函数或者是通过指针、引用访问虚函数。
动多态示例:
演示动态多态性 *
********************************************/
#include
using namespacestd;
class Point
{
private:
int X,Y;
public:
Point(int X=0,intY=0)
{
this->X=X,this->Y=Y;
}
virtual double area() //求面积
{
return0.0;
}
};
const doublePI=3.14159;
class Circle :publicPoint
{
private:
double radius; //半径
public:
Circle(int X, int Y, doubleR):Point(X,Y)
{
radius=R;
}
double area() //求面积
{
returnPI*radius*radius;
}
};
void main()
{
PointP1(10,10);
cout<<"P1.area()="<<P1.area()<<endl;
CircleC1(10,10,20);
cout<<"C1.area()="<<C1.area()<<endl;
Point*Pp;
Pp=&C1;
44 cout<<"Pp->area()="<<Pp->area()<<endl;
45 Point& Rp=C1;
46cout<<"Rp.area()="<<Rp.area()<<endl;
}
运行结果:
P1.area()=0 C1.area()=1256.64 Pp->area()=1256.64 Rp.area()=1256.64
程序解释:
在基类Point中,将area()声明为虚函数。
通过Point类对象P1调用虚函数area()。
通过circle类对象C1调用area(),实现静态联编,调用的是Circle类area()。
第44、46行分别通过Point类型指针与引用调用area(),由于area()为虚函数,此时进行动态联编,调用的是实际指向对象的area()。
注意:
虚函数在使用时,需要注意两个问题
(1)当在派生类中定义了虚函数的重载函数,但并没有重新定义虚函数时,与虚函数同名的重载函数覆盖了派生类中的虚函数。此时试图通过派生类对象、指针、引用调用派生类的虚函数就会产生错误。
(2)当在派生类中未重新定义虚函数,虽然虚函数被派生类继承,但通过基类、派生类类型指针、引用调用虚函数时,不实现动态联编,调用的是基类的虚函数。
(3)虚析构函数
构造函数是不能够设置成为虚函数的,因为在调用构造函数以前,对象还没有实例化,只有当构造函数调用完成后,对象才成为一个名副其实的对象。
而析构函数通常被设置成为虚函数。
定义格式:virtual~类名();
对虚析构函数的说明:
(1)如果基类的析构函数,被声明为虚函数则无论子类的析构函数是否被virtual修饰,都自动成为虚析构函数。
析构函数声明为虚函数后,程序运行时采用动态联编,因此可以确保使用基类类型的指针就能够自动调用适当的析构函数对不同对象进行清理工作。
当使用delete运算符删除一个对象时,隐含着对析构函数的一次调用,如果析构函数为虚函数,则这个调用采用动态联编,保证析构函数被正确执行。
三、抽象类和纯虚函数
(1)抽象类:抽象类是不需要实例化的类,当作为基类时,信息不完整—需要由派生类来定义“缺失的部分”。
抽象类的惟一用途是为其他类提供合适的基类,其他类可从它这里继承和(或)实现接口。
(2)纯虚函数:纯虚函数是一个在基类中声明的没有定义具体实现的虚函数,带有纯虚函数的类称为抽象类。
纯虚函数定义格式:virtual 函数返回值函数名(参数列表)=0;
实际上,它与一般虚函数成员的原型在书写格式上的不同就在于后面加了=0。
C++中,有一种函数体为空的空虚函数,它与纯虚函数的区别为:
①纯虚函数根本就没有函数体,而空虚函数的函数体为空。
②纯虚函数所在的类是抽象类,不能直接进行实例化,空虚函数所在的类是可以实例化的。
③它们共同的特点是都可以派生出新的类,然后在新类中给出新的虚函数的实现,而且这种新的实现可以具有多态特征。
抽象类具有下述一些特点:
(1)抽象类只能作为其它类的基类使用,抽象类不能定义对象,纯虚函数的实现由派生类给出;
(2)派生类仍可不给出所有基类中纯虚函数的定义,继续作为抽象类;如果派生类给出所有纯虚函数的实现,派生类就不再是抽象类而是一个具体类,就可以定义对象。
(3)抽象类不能用作参数类型、函数返回值或强制类型转换。
(4)可以定义一个抽象类的指针和引用。通过抽象类的指针和引用,可以指向并访问各派生类成员,这种访问是具有多态特征的。
举例:
2
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
41
42
|
// p9_9.cpp *
class Shape //抽象类
{
public:
virtual double area()const=0;
virtual void show()=0;
};
class Point:public Shape
{
protected:
int X,Y;
public:
Point(int X=0,int Y=0)
{
this->X=X,this->Y=Y;
}
void show()
{
cout<<"("<<X<<","<<Y<<")"<<endl;
}
double area() const //求面积
{
return 0.0;
}
};
const double PI=3.14159;
class Circle :public Point
{
protected:
double radius; //半径
public:
Circle( int X, int Y,double R):Point(X,Y)
{radius=R; }
double area() const //求面积
{return PI*radius*radius; }
|
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
61
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
void show()
{
cout<<"Centre:"<<"("<<X<<","<<Y<<")"<<endl;
cout<<"radius:"<<radius<<endl;
}
};
class Cylinder: public Circle
{
private:
double height;
public:
Cylinder(int X, int Y, double R, double H):Circle(X,Y,R)
{
height=H;
}
double area() const
{
return 2*Circle::area()+2*PI*radius*height;
}
void show()
{
Circle::show();
cout<<"height of cylinder:"<<height<<endl;
}
};
void main()
{
Cylinder CY(100,200,10,50);
Shape * P;
P=&CY;
P->show();
cout<<"total area:"<<P->area()<<endl;
Circle Cir(5,10,100);
Shape &R=Cir;
R.show();
}
|
radius: 10
height of cylinder:50
totalarea:3769.91
Centre(5,10)
radius:100
程序解释:
程序中将Point、Circle、Cylinder的共同成员函数area()、show()作为Shape中的纯虚函数,形成抽象类Shape。
第73行定义了Shape类型的指针,74行指向Cylindere类对象,第75行通过Shape类型的指针调用了所指向对象中的成员函数。
第78行定义了Shape类型的引用并指向Circle类对象,第79行通过Shape类型的引用调用了所指向对象中的成员函数。