多态是面向对象编程中的一个重要概念,它指的是通过一个基类指针或引用调用一个虚函数时,会根据具体对象的类型来调用该虚函数的不同实现。在多态中,相同的操作可以作用于不同的对象,而具体执行的操作则取决于对象的类型和特性。
多态的好处是可以提高代码的可扩展性和复用性,使得程序更加灵活和通用。多态也是实现多态继承和接口继承的基础。
举个例子:
#include <iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "成年人买票-全价" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "学生买票-半价" << endl;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
Func(p);
Func(s);
return 0;
}
运行结果:
被virtual修饰的类成员函数称为虚函数
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
注意:参数类型相同,但是与函数有无缺省参数和缺省参数相不相等无关
虚函数重写的两个例外:
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮
助用户检测是否重写。
重载:
重载是指在同一个作用域内,函数名相同,但参数列表不同(参数个数不同,或者参数类型不同,或者参数个数和参数类型都不同),返回值类型可相同也可不同的情况。重载可以提高代码的可读性和灵活性,实现不同的功能。重载是一种静态多态,也就是编译时多态,编译器会根据函数名和参数列表来确定调用哪个函数。
重写
指在派生类中,定义了一个与基类虚函数完全相同的函数(函数名,参数列表,返回值类型都相同),或者满足协变返回类型的条件(函数名,参数列表相同,返回值类型为基类虚函数返回值类型的子类型)。覆盖(重写)可以实现动态多态,也就是运行时多态,运行时会根据对象的实际类型来动态绑定虚函数的实现。
隐藏
指在不同的作用域内,定义了一个与另一个函数同名的函数,但参数列表可以相同也可以不同,返回值类型也可以相同也可以不同的情况。隐藏(重定义)会导致同名函数的屏蔽现象,也就是说,如果在派生类中定义了一个与基类同名的函数,那么基类的同名函数就会被隐藏,无论基类的函数是否为虚函数。如果要调用被隐藏的函数,需要使用作用域运算符::来指定。
在虚函数的后面写上 = 0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口
类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成
多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
派生类的虚函数表是怎么生成的呢?
当传父类的的对象时,调用父类自己的虚函数;传子类的对象时,将子类的对象赋值给父类,但子类已经完成虚函数的重写,子类的虚函数表会覆盖继承自父类的虚函数表,这样调用的便是子类的虚函数了,从而实现了多态调用。