多态入门
1. 多态的概念
- 多态概念:完成某个行为,不同的对象执行时会产生出不同的形态
- 例如:买票行为。普通人买票是全价票;学生买票是半价票;军人买票是优先买票
2. 多态的条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
比如Student继承了Person。Person对象买票全价,Student对象买票半价。
继承中构成多态的条件:
- 必须通过基类的 指针或者引用 调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
#include <iostream>
class Person{
public:
virtual void BuyTicket()
{
std::cout << "全价票" << std::endl;
}
};
class Student : public Person{
public:
virtual void BuyTicket()
{
std::cout << "半价票" << std::endl;
}
};
void test()
{
Person p, *pp;
Student s;
pp = &p;
pp->BuyTicket();
pp = &s;
pp->BuyTicket();
}
输出:
全价票 半价票
3. 虚函数及其重写(覆盖)
3.1 虚函数
- 虚函数:即用
virtual
修饰的类成员函数
class Person{
public:
virtual void BuyTicket(){ std::cout << "全价票" << std::endl; }
};
3.2 虚函数覆盖
- 虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
class Person{
public:
virtual void BuyTicket(){ std::cout << "全价票" << std::endl; }
};
class Student : public Person{
public:
virtual void BuyTicket(){ std::cout << "半价票" << std::endl; }
};
- 注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不规范,不建议这样使用
class Person{
public:
virtual void BuyTicket(){ std::cout << "全价票" << std::endl; }
};
class Student : public Person{
public:
void BuyTicket(){ std::cout << "半价票" << std::endl; }
};
3.3 虚函数覆盖中的特例
①协变(父子类中重写虚函数的返回值不同)
- 子类重写父类虚函数时,与父类虚函数返回值类型不同。
协变
:即父类虚函数返回另一个父类对象的指针或者引用,子类虚函数返回另一个父类的子类对象的指针或者引用时
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;}
};
②虚析构函数的重写(父子类中虚析构函数函数名不同)
- 如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写.
- 虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
class Person {
public:
virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
virtual ~Student() { cout << "~Student()" << endl; }
};
void test()
{
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
}
输出:
~Person() ~Student() ~Person()
3.4 override 和 final
- C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
① final:修饰虚函数,表示该函数不能再被重写
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒适" << endl;}
};
报错:error C3248: “Car::Drive”: 声明为“final”的函数无法被“Benz::Drive”重写
② override :检查子类虚函数是否重写了父类某个虚函数,如果没有重写编译报错
class Car
{
public:
virtual void Drive() {}
};
class Benz :public Car
{
public:
// 函数名拼写错误
virtual void Drve() override{ cout << "Benz-舒适" << endl; }
};
报错:error C3668: “Benz::Drve”: 包含重写说明符“override”的方法没有重写任何基类方法
4. 重载 覆盖 隐藏 的对比
5. 抽象类
5.1 抽象类概念
- 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
- 派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
- 纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
class BMW :public Car
{
public:
virtual void Drive()
{
cout << "BMW-操控" << endl;
}
};
void test()
{
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
}
输出
Benz-舒适 BMW-操控
5.2 接口继承与实现继承
- 普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
- 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
- 如果不实现多态,不要把函数定义成虚函数。