一、多态的基本概念
基类指针只能调用基类的成员函数,不能调用派生类的成员函数。
如果在基类的成员函数前加virtual 关键字,把它声明为虚函数,基类指针就可以调用派生类中同名的成员函数,通过派生类中同名的成员函数,就可以访问派生对象的成员变量。
有了虚函数,基类指针指向基类对象时就使用基类的成员函数和数据,指向派生类对象时就使用派生类的成员函数和数据,基类指针表现出了多种形式,这种现象称为多态。
基类引用也可以使用多态。
注意:
1)只需要在基类的函数声明中加上virtual关键字,函数定义时不能加。
2)在派生类中重定义虚函数时,函数特征要相同。
3)当在基类中定义了虚函数时,如果派生类没有重定义该函数,那么将使用基类的虚函数。
4)在派生类中重定义了虚函数的情况下,如果想使用基类的虚函数,可以加类名和域解析符。
5)如果要在派生类中重新定义基类的函数,则将它设置为虚函数;否则,不要设置为虚函数,有两方面的好处:首先效率更高;其次,指出不要重新定义该函数。
示例:1、基类指针指向基类对象
class People
{
public:
int ID = 0;//ID号
virtual void show() //声明为虚函数
{cout<<"People::show():我是"<<ID<<"号。"<<endl;}
virtual void show(int a)
{cout<<"People::show(int a):我是"<<ID<<"号。"<<endl;}
};
class Boy:public People
{
public:
int age = 0;//年龄
void show()
{cout<<"boy::show():我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
void show(int a)
{cout<<"boy::show(int a):我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
};
int main() {
People a;a.ID = 3;//创建基类对象并对成员赋值
Boy b;b.age =18;b.ID = 6;//创建派生类对象并对成员赋值
People *p;//声明基类指针
p=&a;p->show();//让基类指针指向基类对象,并调用虚函数。
//p=&b;p->show();//让基类指针指向派生类对象,并调用虚函数。
p->show(5);
p->People::show(5);
return 0;
}
2、指向派生类对象
class People
{
public:
int ID = 0;//ID号
virtual void show() //声明为虚函数
{cout<<"People::show():我是"<<ID<<"号。"<<endl;}
virtual void show(int a)
{cout<<"People::show(int a):我是"<<ID<<"号。"<<endl;}
};
class Boy:public People
{
public:
int age = 0;//年龄
void show()
{cout<<"boy::show():我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
void show(int a)
{cout<<"boy::show(int a):我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
};
int main() {
People a;a.ID = 3;//创建基类对象并对成员赋值
Boy b;b.age =18;b.ID = 6;//创建派生类对象并对成员赋值
People *p;//声明基类指针
//p=&a;p->show();//让基类指针指向基类对象,并调用虚函数。
p=&b;p->show();//让基类指针指向派生类对象,并调用虚函数。
p->show(5);
p->People::show(5);//使用派生类的成员函数和变量
return 0;
}
二、多态的应用场景
示例:创建基类指针,让它指向派生类对象,用基类指针调用派生类的成员函数
using namespace std;
class Hero // 英雄基类
{
public:
int viability; // 生存能力。
int attack; // 攻击伤害。
virtual void skill1() { cout << "英雄释放了一技能。\n"; }
virtual void skill2() { cout << "英雄释放了二技能。\n"; }
virtual void uskill() { cout << "英雄释放了大绝招。\n"; }
};
class XS :public Hero // 西施派生类
{
public:
void skill1() { cout << "西施释放了一技能。\n"; }
void skill2() { cout << "西施释放了二技能。\n"; }
void uskill() { cout << "西施释放了大招。\n"; }
};
class HX :public Hero // 韩信派生类
{
public:
void skill1() { cout << "韩信释放了一技能。\n"; }
void skill2() { cout << "韩信释放了二技能。\n"; }
void uskill() { cout << "韩信释放了大招。\n"; }
};
class LB :public Hero // 李白派生类
{
public:
void skill1() { cout << "李白释放了一技能。\n"; }
void skill2() { cout << "李白释放了二技能。\n"; }
void uskill() { cout << "李白释放了大招。\n"; }
};
int main()
{
// 根据用户选择的英雄,施展一技能、二技能和大招。
int id = 0; // 英雄的id。
cout << "请输入英雄(1-西施;2-韩信;3-李白。):";
cin >> id;
// 创建基类指针,让它指向派生类对象,用基类指针调用派生类的成员函数。
Hero* ptr = nullptr;
if (id == 1) { // 1-西施
ptr=new XS;
}
else if (id == 2) { // 2-韩信
ptr = new HX;
}
else if (id == 3) { // 3-李白
ptr = new LB;
}
if (ptr != nullptr) {
ptr->skill1();
ptr->skill2();
ptr->uskill();
delete ptr;
}
}
三、多态的对象模型
类的普通成员函数的地址是静态的,在编译阶段已指定。
如果基类中有虚函数,对象的内存模型中有一个虚函数表,表中存放了基类的函数名和地址。
如果派生类中重定义了基类的虚函数,创建派生类对象时,将用派生类的函数取代虚函数表中基类的函数。
C++中的多态分为两种:静态多态与动态多态。
静态多态:也成为编译时的多态;在编译时期就已经确定要执行了的函数地址了;主要有函数重载和函数模板。
动态多态:即动态绑定,在运行时才去确定对象类型和正确选择需要调用的函数,一般用于解决基类指针或引用派生类对象调用类中重写的方法(函数)时出现的问题。
四、类多态--如何析构派生类
构造函数不能继承,创建派生类对象时,先执行基类构造函数,再执行派生类构造函数。
析构函数不能继承,而销毁派生类对象时,先执行派生类析构函数,再执行基类析构函数。
派生类的析构函数在执行完后,会自动执行基类的析构函数。
如果手工的调用派生类的析构函数,也会自动调用基类的析构函数。
析构派生类的要点如下:
1)析构派生类对象时,会自动调用基类的析构函数。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉。
2)析构函数可以手工调用,如果对象中有堆内存,析构函数中以下代码是必要的:
delete ptr;
ptr=nulllptr;
3)用基类指针指向派生类对象时,delete基类指针调用的是基类的析构函数,不是派生类的,如果希望调用派生类的析构函数,就要把基类的析构函数设置为虚函数。
4)C++编译器对虚析构函数做了特别的处理。
5)对于基类,即使它不需要析构函数,也应该提供一个空虚析构函数。
6)赋值运算符函数不能继承,派生类继承的函数的特征标与基类完全相同,但赋值运算符函数的特征标随类而异,它包含了一个类型为其所属类的形参。
示例:
class AA{
public:
AA(){cout<<"class AA()"<<endl;}
virtual void func(){cout<<"AA func()"<<endl;}
virtual ~AA(){cout<<"class ~AA()"<<endl;}//设置为虚函数
};
class BB : public AA
{
public:
BB(){cout<<"class BB()"<<endl;}
void func(){cout<<"BB func()"<<endl;}
~BB(){cout<<"class ~BB()"<<endl;}
};
int main()
{
//1.手动调用派生类的析构函数,也会自动调用基类的析构函数
// BB* b=new BB;
// b->~BB();
// delete b;
//2.用基类指针指向派生类对象,需要将基类中的析构函数设置为虚函数,否则将不调用基类的析构函数
AA* a= new BB;
delete a;
return 0;
}
五、类多态--纯虚函数和抽象类
纯虚函数只有函数名、参数和返回值类型,没有函数体,具体实现留给该派生类去做。
语法:virtual 返回值类型 函数名 (参数列表)=0;
含有纯虚函数的类被称为抽象类,不能实例化对象,可以创建指针和引用。
派生类必须重定义抽象类中的纯虚函数,否则也属于抽象类。
基类中的纯虚析构函数也需要实现。
class AA{
public:
AA(){cout<<"class AA()"<<endl;}
virtual void func()=0;//{cout<<"AA func()"<<endl;}
virtual ~AA(){cout<<"class ~AA()"<<endl;}
};
class BB : public AA
{
public:
BB(){cout<<"class BB()"<<endl;}
void func(){cout<<"BB func()"<<endl;}
~BB(){cout<<"class ~BB()"<<endl;}
};
int main()
{
BB b;
//1.使用基类指针指向对象
AA *p = &b;
p->func();
//2.使用引用
// AA &p =b;
// p.func();
return 0;
}