目录
前言
在看多态之前,建议先看看往期关于继承的讲解,因为多态这里需要用到继承,而继承也是实现多态的基础
实际场景
1、小明和他父亲今年过年打算回老家,于是它们两个都用各自的身份证去app上买票,但它们两个买票的价格不同,小明父亲是全价买票,但小明作为一个学生买票是半价。那么卖票的app是怎么分辨它们各自的身份买票的价格呢?要实现这个逻辑我们可以使用多态的方式
2、张三的女朋友小美是一个资深的支付宝用户,有一天,支付宝推出了一个活动“扫码抢红包”,小美扫了几毛钱,抱着尝试的心态,小美叫张三也来试试,但张三没有用过支付宝。于是小美就帮他注册了一个新的支付宝账号并且绑定实名。当张三去参加这个扫码抢红包的活动时,居然扫到了20块钱,小美很好奇为什么会这样,于是她又叫了几个朋友去参加这个活动。最终发现一个规律:为了吸引用户,越不经常用支付宝的人扫到的红包越多。那么支付宝是如何实现这个逻辑的呢?其实这个逻辑也可以用多态的方式完成
多态的定义
所谓多态,就是在不同继承关系的类对象,去调用同一个函数,产生了不同的行为。
例如学生继承了人,人买票全价,学生买票半价
如下代码实现的就是多态
class Person
{
public:
virtual void BuyTicket()
{
std::cout << "买票-全价" << std::endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
std::cout << "买票-半价" << std::endl;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
func(p);
func(s);
//运行结果:
//买票-全价
//买票-半价
return 0;
}
上述代码,实现了多态调用,即传给func的对象类型是Person,就调用Person的买票函数,传给func的对象类型是Student,就调用Student的买票函数。
(上述代码在构成多态的规则章节会一直用)
我们已经看到了多态的简单场景,那么怎么样才能实现多态呢?
很显然,要构成多态是需要一些规则的,我们接下来就是要详细了解这些规则,以及一些特殊的情况
构成多态的规则
规则1:
构成多态的两个函数必须满足虚函数重写规则
首先,我们需要先了解一下什么是虚函数
虚函数:被virtual修饰的函数就是虚函数
重写:就是在一个父子继承类中的同名函数的返回值和参数类型都相同,总结一下就是三同(返回值、参数类型、名字),并且满足重写的两个函数必须是虚函数,例如上面的代码实现中,Person类和Student类的Buyticket函数就满足重写,并且我们称子类的虚函数重写了父类的虚函数
规则2:
必须通过基类的指针或者引用调用虚函数
也就是说在上面的代码实现中,如果func函数的形参不是Person&或Person*,那么就无法实现多态调用
上述,我们说的两个规则都是普遍情况,但有时候会出现特殊情况,也就是不满足上述两个规则但也构成多态的情况
特殊情况
第一种:协变
协变说的是在有些情况下,虚函数的返回值不同也能构成多态
但协变也有要求,就是虚函数的返回值必须是父子类关系的指针或者引用,如下代码
class Person
{
public:
virtual Person& BuyTicket()
{
std::cout << "买票-全价" << std::endl;
return *this;
}
};
class Student : public Person
{
public:
virtual Student& BuyTicket()
{
std::cout << "买票-半价" << std::endl;
return *this;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
func(p);
func(s);
//运行结果:
//买票-全价
//买票-半价
return 0;
}
上述代码演示的协变是父子类关系的引用,而父子类关系的指针同样也可以
第二种:析构函数的重写
析构函数的重写说的是两个类的析构函数名字不相同,但它们可以构成重写关系
如下代码A类和B类的析构函数构成重写
class A
{
public:
virtual ~A()
{
std::cout << "~A()" << std::endl;
}
};
class B : public A
{
public:
virtual ~B()
{
std::cout << "~B()" << std::endl;
}
};
对于析构函数的重写这种特殊情况,其实是有意义的,如下场景中就一定要用到多态,而多态就要满足虚函数的重写
class A
{
public:
~A()
{
std::cout << "~A()" << std::endl;
}
};
class B : public A
{
public:
~B()
{
std::cout << "~B()" << std::endl;
}
};
int main()
{
A* pA = new A;
delete pA;
A* pB = new B;
delete pB;
//运行结果:
//~A()
//~A()
return 0;
}
我们可以看到,在上述代码中,pA指向的对象只调用一次A类的析构是没问题的,但pB指向的对象是一个B类型,正常来说是要先调用一次B类析构,再调用一次A类析构,但上述代码调用析构函数的时候只调用了一次A类的析构函数,B类的析构函数并没有调用,这是由于上述代码是普通调用,而不是多态调用,对于普通调用是根据指针的类型来调用析构的,多态调用是根据指针指向的类型,上述场景就