【本节内容】
1.多态的概念
2.多态的定义及实现(虚函数重写)
3.抽象类
4.C++11 override和final
简单说 多态就是同一个函数表现出的多种形态。具体点 就是当不同的对象去完成某个相同行为时所产生不同的状态。
举例:就生活中买票这个行为,当成人去完成时买到全价票,当学生去完成时买到半价票,当儿童去完成时是免费票。
1.调用函数的对象必须是指针或引用
2.被调用的函数必须是虚函数,且已完成了虚函数的重写
这里引入一个问题:什么是虚函数?
例如下面的BuyTicket()函数就是虚函数:
class Person
{
public:
virtual void BuyTicket() { cout << "全价票" << endl; }
};
那么什么是虚函数的重写?
这里的完全相同是指:函数名,参数,返回值都相同。
例如:下面的Student 类就将Person 类的BuyTicket()函数重写了:
class Person
{
public:
virtual void BuyTicket() { cout << "全价票" << endl; }
};
class Student :public Person
{
public:
virtual void BuyTicket() { cout << "半价票" << endl; }
};
虚函数重写还有一个例外:重写的虚函数返回值可以不同,但是必须同为指针或引用。
这个例外我们了解一下就好了,毕竟用到的地方不多。
class A{};
class B :public A{};
class Person
{
public:
virtual A* f() { return new A; } //返回值A*
};
class Student :public Person
{
public:
virtual B* f() { return new B; } //返回值B*
};
有时我们可能看到的子类中重写的虚函数没有virtual关键字,这是允许的,因为父类的虚函数被子类继承下来以后仍然保持虚函数的属性,我们只是重写了它。但这是非常不规范的,不建议大家这样写。
我们建议将父类的析构函数写成虚函数,这样子类的析构函数就重写了父类的析构函数。这里子类与父类的函数名虽然不同,但仍然构成了重写,因为编译器会对析构函数的名字做特殊处理,编译后的析构函数名统一为destructor,所以可以将父类的析构函数写成虚函数。
class Person
{
public:
virtual ~Person() { cout << "~Person" << endl; }
};
class Student :public Person
{
public:
virtual ~Student() { cout << "~Studennt" << endl; }
//可不加virtual关键字,但不建议这样做
};
只有子类的析构函数重写了父类的析构函数,下面的delete对象调用析构函数时
才能构成多态,才能保证p1和p2指向的对象正确调用析构函数
int main()
{
Person *p1 = new Person; //指向父类的指针
Person *p2 = new Student; //指向子类的指针
delete p1;
delete p2;
system("pause");
return 0;
}
若子类未与父类构成多态,则 Person *p = new Student; delete p;
只会调用父类的析构函数,子类成员未清理,此时会造成内存泄漏!!
普通函数的继承是一种实现继承,因为子类继承的父类成员函数用于函数实现。虚函数的继承是一种接口继承,因为子类继承的父类虚函数用于重写达成多态。
定义虚函数的主要目的就是实现多态,不实现多态就不要定义虚函数。
那么现在,看下面两个代码,你知道哪个构成了多态吗?
class Person
{
public:
virtual void BuyTicket()
{
cout << "全价票" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTicket()
{
cout << "半价票" << endl;
}
};
void func(Person &p)
{
p.BuyTicket();
}
相信认真看了我前面内容的伙伴都知道的,A构成了多态,因为它是用不同继承关系的类对象去调用同一函数,最后达到不同的效果。
而在B中,虽然也是不同继承关系的类对象去调用函数,但它们调用的只是相同函数,并不是同一个函数,所以不构成多态。
在虚函数的后面写上 =0,则这个函数就是纯虚函数。
包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。 即使被子类继承后也不能实例化出对象,只有重写了纯虚函数的子类才能实例化出对象。所以纯虚函数更体现出了接口继承。
由此看出纯虚函数的作用:
1)实现抽象类2)强制子类重写虚函数
class Car
{
public:
virtual void Drive() = 0; //纯虚函数
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "舒适" << endl; }
};
class BMW :public Car
{
public:
virtual void Drive() { cout << "简单" << endl; }
};
void Test()
{
//Car c; //Car是抽象类,无法实例化对象
Car *p1 = new Benz;
p1->Drive();
Car *p2 = new BMW;
p2->Drive();
}
强制子类重写虚函数有两种方法:
1)基类使用纯虚函数 2)用override修饰虚函数
override 修饰派生类虚函数强制完成重写,如果没有重写,编译会报错。
实际中建议大家多使用 纯虚函数+override
的方式来强制重写虚函数,因为虚函数的意义就是实现多态,如果没有重写那虚函数就没有了意义。
class Car
{
public:
virtual void Drive() {}
};
class Benz :public Car
{
public:
virtual void Drive() override { cout << "舒适" << endl; }
};
final 修饰基类的虚函数不能被派生类重写
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "舒适" << endl; } //编译无法通过!
};
final 关键字在我的博客 继承(二)里也提到了,并涉及了一道面试题:
实现一个不能被继承的类:https://blog.csdn.net/ly_6699/article/details/88805277
有兴趣的伙伴可以点链接去了解。
我在下篇博客多态(二):https://blog.csdn.net/ly_6699/article/details/88928006 中,将继续讲多态的知识点,包括多态的原理和虚函数表的内容,希望大家多多关注和留言交流哟~