1.多态的概念
多态即多种形态,对于C++程序设计中指的是在类的实例化对象中,当不同的对象去完成某个行为时会出现不同的状态。
2.多态的分类
- 静态的多态:函数重载,看起来调用同一个函数有不同行为。静态:原理是编译时实现。
- 动态的多态:一个父类的引用或指针去调用同一个函数,传递不同的对象,会调用不同的函数。动态:原理运行时实现。
3.多态的定义
3.1构成多态的两个必要条件!!!
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
3.2虚函数
虚函数:即被virtual修饰的类成员函数称为虚函数。如下示例:
cpp
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
3.3虚函数的重写
派生类中有一个跟基类完全相同的虚函数,即子类中满足三同(函数名、参数、返回值)相同的虚函数,叫做重写(覆盖)
在派生类中虚函数函数重写构成多态的示例及注意事项如下:
cpp
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时
,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)
,但是该种写法不是很规范,不建议这样使用*/
/*void BuyTicket() { cout << "买票-半价" << endl; }*/
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
//1、构成多态,跟p的类型没有关系,传的哪个类型的对象,
//调用的就是这个类型的虚函数 -- 跟对象有关
//2、不构成多态,调用就是p类型的函数 -- 跟类型有关
void Func(Person& p)
{ p.BuyTicket(); }
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
4.虚函数重写的两个例外
4.1协变(基类与派生类虚函数返回值类型不同)。要求返回值是父子关系的指针或者引用
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
cpp
class A{};
class B : public A {};
class Person {
public:
virtual A* f() {return new A;}
};
class Student : public Person {
public:
virtual B* f() {return new B;}
};
4.2析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
cpp
class Person {
public:
virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
virtual ~Student() { cout << "~Student()" << endl; }
};
//只有派生类Student的析构函数重写了Person的析构函数,
//下面的delete对象调用析构函数,才能构成多态,
//才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
// 普通对象,析构函数是否虚函数,是否完成重写,都正确调用了
// Person p;
// Student s;
// 动态申请的对象,如果给了父类指针管理,那么需要析构函数是虚函数
Person* p1 = new Person; // operator new + 构造函数
Person* p2 = new Student;
// 虚函数的重写允许,两个都是虚函数或者父类是虚函数,再满足三同,就构成重写。
// 其实这个是C++不是很规范的地方,当然我们建议两个都写上virtual
// 本质上,子类重写的虚函数,可以不加virtual是因为析构函数,设计初衷
// 父类析构函数加上virtual,那么就不存在不构成多态,没调用子类析构函数,内存泄漏场景
// 建议,我们自己写的时候,都加上virtual,肯定没毛病
// 析构函数 + operator delete
// p1->destructor()
delete p1;
delete p2;
// p2->destructor()
return 0;
}