多态的实现
多态的特性主要体现在“一种接口,多种实现”,通过统一的接口定义,来匹配各种特殊的处理方式。它的应用范围很广,是面向对象编程非常重要的思想。
条件:继承、重写、父类指针指向派生类
重写(Override)与重载(Overload)的区别:重写是派生类对父类函数的多态性表现,也就是派生类具有特殊的父类行为;重载是同一个类(或者全局函数等)中同名函数通过形参不同实现匹配。最大区别可以理解为:重写看对象,重载看形参。
多态实现:
#include<iostream>
using namespace std;
class person
{
public:
virtual void show() //加virtual表明实现多态,否则不会被派生类改写。
{
cout<<"person show"<<endl;
}
};
class student:public person
{
public:
void show() //这是重写,用来覆盖父类的函数,实现多态
{
cout<<"student show"<<endl;
}
void show(int c)// 这是重载,参数不同
{
cout<<c<<": student show"<<endl;
}
};
int main()
{
person *lp,p;
student s,*lq;
lp=&s; //父类的指针指向派生类是可以,可以理解成把s 这个student 看成 person 。
lq=(student *)&p;//同时,派生类指针特殊情况下可以指向父类对象。
//强制转换符合语法,但是这里显然不合理,除非,p确实是一个student对象
lp->show(); //父类中函数如果不加virtual 还会调用父类的show函数
//加virtual 则实现多态,父类指针指向不同的派生类,实现特殊功能。
lq->show();//只能调用父类的show
return 0;
}
1、virtual 称为虚函数,实现多态要加上,否则指向派生类对象的父类指针不能完成多态;
2、父类指针指向派生类需要好好理解,派生类指针指向父类对象在某些应用场景会出现;
3、派生类重写的虚函数会继承虚函数的特征,上例中student的show函数自动带有虚函数的特征,可以被它的子类重写。
虚函数
多态里面最重要理解virtual
class person
{
public:
virtual void f(){}
virtual void g(){};
void k(){};
};
class student:public person
{
public:
void f() {}
void t(){};
};
先画父类和派生的实例示意图:
1、父类有虚函数,则父类及派生的对象在实例化(定义出对象)的时候,都会给每个对象添加一个虚函数表的指针,并且指向该类的虚函数表;
2、父类的指针指向派生类对象,该指针调用虚函数的时候,找到的实例是派生类,因此使用派生类的虚函数表指针去加载相应的函数,从而实现多态。如上例中:student 改写了f(),继承了g();
3、构造函数不能写能虚函数,虚函数是在构造出对象(实例化)后 “补上” 的虚函数调用表,对象都没有虚函数表指针不存在。 对象需要构造函数才能产生,构造函数则需要对象生成后加载虚函数表;
4、析构函数可以写成虚函数(建议使用),可能在new 和 delete中出现问题:
person *lp=new student();
delete lp;
删除堆中new出的对象的时候,如果该对象的析构函数不是虚函数,则只调用父类(person)的析构函数;如果把父类的析构函数写成虚函数,则首先调用派生类(student)的析构函数,再调用父类的析构函数(与栈中回收对象相同)。因此,推荐父类的析构函数(派生类自动继承虚函数特征)写成虚函数。
纯虚函数和抽象类
父类中不能实现虚函数功能(空的函数体也不行)情况下,可以定义成纯虚函数:
virtual 函数类型 函数名(形参列表) = 0;
也就是该虚函数加上 “ = 0 ” 的标记。
类中定义了纯虚函数,则该类称为抽象类,不能实例化(可以定义指针,这要区别)
继承于抽象类的派生类,如果不重写(实现)父类的纯虚函数,派生类也是一个抽象类。
纯虚函数应该属于C++的 “接口编程思想”,要使用抽象类的功能,不仅仅继承于抽象类,还必须实现指定的纯虚函数,其实就是接口编程是思想(JAVA的事件侦听器)
#include<iostream>
using namespace std;
class person
{
public:
virtual void f(){}
virtual void g()=0;
void k(){};
};
class student:public person
{
public:
void g(){};//需改写纯虚函数,否则也是抽象类
void f() {}
void t(){};
};
int main()
{
//person y; 错误,抽象类不能实例化
//person *q =new person(); 错误,抽象类不能实例化
student x;
person *p =new student();//正确,定义指针,new 的是派生类对象
return 0;
}