四 多态性与虚函数
1.多态性
多态性:对同一的消息,不同对象有不同的响应方式。
多态性分两种:静态多态性 和 动态多态性。
静态多态性
是通过函数的重载
实现的,在程序编译时
系统就能决定要调用的是哪个函数,也称编译时的多态性。
动态多态性
是通过虚函数
实现的,在程序运行过程中
才动态地确定操作所针对的对象。
- 坐公交车前确定了路线就好比静态多态性,而临时决定路线就好比动态多态性。
2.虚函数
在类的继承层次结构中,若派生类和基类存在成员函数相同的情况,编译系统按照同名覆盖的原则决定调用的对象。
虚函数:在基类声明函数是虚拟的,并不是实际存在的函数,然后在派生类中才正式定义此函数。
虚函数的作用是:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数
。(非虚函数,指向基类的指针可以指向派生类,但只能引用派生类中继承的基类成员)
(1)虚函数定义
定义虚函数:
class Student
{public:
virtual void display();
...
}
class Student1: public Student
{public:
void display();
...
}
在main函数中:
Student stud, *p;
Student1 stud1;
p = &stud;
p->display(); // 基类的display
p = &stud1;
p->display(); // 派生类的display
// 如果基类display不是虚函数,那么上面两次调用的display均为基类的display。
这就是 多态性:对同一的消息,不同对象有不同的响应方式。
注意
:
<1>在类体中声明成员函数为virtual,在类体外定义时不必再加virtual;
<2>在派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型,必须与基类的虚函数相同,根据派生类的需要重新定义函数体;
<3>定义一个指向基类对象的指针变量,并使它指向同一类组中需要调用该函数的对象;
<4>通过该指针调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
重载与虚函数的区别:
<1>函数重载处理的是同一层次上的同名函数问题,而 虚函数
是处理 不同派生层次上
的同名函数问题;前者是横向重载,后者是纵向重载;
<2>同一类组的 虚函数的首部是相同的
,而函数重载时函数的首部是不同的(参数个数或类型不同)。
(2)关联
确定调用的具体对象的过程称为关联。
编译时确定的关联称为静态关联(或早期关联),运行阶段时称为动态关联(滞后关联)。
(3)虚函数的使用要求
<1>只能是声明 类的成员函数为虚函数
,类外的普通函数不行。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义;
<2>一个成员函数被声明为虚函数之后,在同一类族中就 不能再定义一个非virtual
但与该虚函数首部相同的 同名函数
。
什么情况下考虑虚函数:
<1>首先看成员函数所在类是否为基类,再看该成员函数在派生类中是否会被修改,若两者都是,则虚之;
<2>如果成员函数在派生类中不被修改或根本用不到,则不虚之;
<3>该成员函数通过基类指针去访问,则虚之;
<4>声明虚函数,但不定义结构体,留着派生类定义。
注意
:虚函数有空间开销,系统为含虚函数的类创建一个虚函数表,它是指针数组,存放每个虚函数的入口地址。
(4)虚析构函数
在程序中,最好把基类的析构函数声明为虚函数
,这将使所有派生类的析构函数自动成为虚函数。
3.纯虚函数与抽象类
(1)纯虚函数
有时基类中的虚函数,并不是积累本身需要,而是 考虑到派生类的需要
,实现多态性。在基类中预留,在派生类中定义。
一般形式:
virtual 函数类型 函数名(参数列表)=0;
virtual void display()=0;
注意
:
<1>纯虚函数没有函数体;
<2>“=0”是告诉编译系统,这是个纯虚函数;
<3>纯虚函数只有函数名不具备函数功能,不能被调用;
<4>如果派生类没有对基类的纯虚函数做定义,则该纯虚函数在派生类中仍为纯虚函数。
(2)抽象类
不用来定义对象只用作继承的类
,称为抽象类。凡是包含纯虚函数的类都是抽象类。因为纯虚函数无法被调用,所以包含纯虚函数的类不能创建对象。
注意
:虽然抽象类不能实例化,但是可以定义指向抽象类数据的指针变量。