🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻强烈推荐优质专栏: 🍔🍟🌯C++的世界(持续更新中)
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解C++中多态知识,不同对象不同的处理方式.
金句分享:
✨不要总是抱怨环境,鼓起勇气战胜它!✨
前言
多态的概念:就是多种形态,当不同的对象去完成某个动作时会产生出不同的状态。
比如:在现实生活中,我们经常遇到不同身份
的人享受的待遇不一样.
购买回家的火车票时,如果是购买的学生票
,会有打折优惠.
在参加某宝,某团的活动时,新用户
获得的优惠券就会比活跃用户
的额度高不少.
…
C++
中的多态(polymorphism)指的是同一个函数名在不同对象上可以有不同的行为。
它可以通过两种方式实现:
(1)虚函数(virtual function
)
虚函数是在基类中声明的函数,它可以被派生类重写,实现多态的效果。在派生类中实现的函数可以覆盖基类中的同名函数,而且会在运行时的对象类型上调用合适的函数。通过将基类指针或引用指向派生类对象,可以实现动态多态性。
(2)模板(template
)。(前面已经讲过了)
模板是一种通用的代码库,可以为不同的类型提供相同的代码实现。使用模板可以实现静态多态性。在编译期间,编译器会依据模板中使用的类型,生成适当的代码。
使用多态可以让代码更加灵活,易于维护和扩展。此外,多态也是对象导向程序设计中的核心概念之一。
目录
- 前言
- 一、多态的实现
- 🍭实现多态的条件:
- 二、何为虚函数?
- 🍉就你小子特殊?
- 2.1 析构函数可以是虚函数吗?
- 三、 关键字override 和 final
一、多态的实现
代码1,2,3的执行结果是什么?
class People
{
public:void Have_lunch(){cout << "你需要支付10元的午餐费!" << endl;}
};class Student:public People
{
public:void Have_lunch(){cout << "你需要支付8元的午餐费!" << endl;}
};class Teacher:public People
{
public:void Have_lunch(){cout << "老师,您好,欢迎就餐!已扣除5元餐费." << endl;}
};void test1(People& p1)
{p1.Have_lunch();
}int main()
{People p1;Student s1;Teacher t1;test1(p1); //1test1(s1); //2test1(t1); //3return 0;
}
运行结果:
你需要支付10元的午餐费!
你需要支付10元的午餐费!
你需要支付10元的午餐费!
在前面的继承章节,我们知道,基类和派生类是两个不同的作用域,定义同名的两个函数时,会形成隐藏操作.所以这里的打印结果都是一样的.
如果想实现多态,也就是不同的对象调用同一个函数,实现不同的结果,以下代码就是实现的例子,试着观察一下区别吧.
运行结果:
你需要支付10元的午餐费!
你需要支付8元的午餐费!
老师,您好,欢迎就餐!已扣除5元餐费.
🍭实现多态的条件:
-
继承关系:多态需要通过继承关系来实现,子类必须要继承父类。
-
方法重写(
虚函数
实现):子类必须重写父类的方法,这样才能表现出不同的行为。 -
父类指针/引用指向子类对象:必须是父类的指针或者引用调用虚函数,才能进行多态操作。
二、何为虚函数?
C++
中的虚函数是一种特殊的成员函数,用于在继承关系中实现多态性。在父类中通过关键字virtual
声明的函数为虚函数,子类可以覆盖并重新实现(重写)该函数。当通过父类的指针或引用调用虚函数时,实际调用的是子类中的实现,而不是父类的实现。这样就实现了多态.
普通函数:
virtual void Have_lunch(){cout << "你需要支付8元的午餐费!" << endl;}
虚函数:
virtual void Have_lunch(){cout << "你需要支付8元的午餐费!" << endl;}
虚函数的重写要求十分严格:
返回类型
要相同:
参数类型
要相同:
函数名
相同:这个就不演示了,肯定无法实现多态.
🍉就你小子特殊?
虚函数
的特殊情况: 斜变
派生类重写基类虚函数时,与基类虚函数返回值类型不同。
基类虚函数返回基类对象的指针或者引用.
派生类虚函数返回派生类对象的指针或者引用时.
这种情况,即使返回值不同,此时也满足虚函数
的重写,也可以实现多态
.
//斜变
class People
{
public:virtual People* Have_lunch() //返回基类的指针或者引用{cout << "你需要支付10元的午餐费!" << endl;return nullptr;}
};class Student :public People
{
public:virtual Student* Have_lunch() //返回派生类的指针或者引用{cout << "你需要支付8元的午餐费!" << endl;return nullptr;}
};class Teacher :public People
{
public:virtual Teacher* Have_lunch() /返回派生类的指针或者引用{cout << "老师,您好,欢迎就餐!已扣除5元餐费." << endl;return nullptr;}
};
运行结果:
2.1 析构函数可以是虚函数吗?
显然,基类与派生类析构函数的名字不同 .
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成
destructor
。即所有对象的析构函数,在编译后,都被处理为统一的名字:destructor
.
- 为什么析构函数名要被统一转化为
destructor
?
因为要实现多态需要函数名相同.
- 为什么析构函数要实现多态?
因为析构函数实现多态了以后,才能实现在析构基类和派生类时,各自调用自己的析构函数,防止内存泄漏!
示例:
class People
{
public:~People(){free(arr);cout << "~People()" << endl;}
private:int* arr = new int[10];
};class Student :public People
{
public:~Student(){free(arr2);cout << "~Student()" << endl;}
private:int* arr2 = new int[10];
};int main()
{People* p1 = new People; People* p2 = new Student; //基类指针指向派生类delete p1;delete p2; //arr2未释放return 0;
}
运行结果:
显然,在未实现多态的情况下,当基类指针指向派生类时,调用析构函数都只能调用基类的析构函数.这就导致了派生类存在成员变量并没有释放空间,也就导致了内存泄漏
!
综上,析构函数可以是虚函数,而且还强烈建议将析构函数写成虚函数,实现多态.
虚函数重写需要遵守以下条件:
- 函数名称、参数列表和返回类型在父类和子类中必须完全相同。(三同)
- 函数在父类中必须被声明为
virtual
关键字,否则在子类中重写将不会产生多态效果。 - 函数在子类中必须使用
virtual
关键字进行声明,以便在运行时确定需要调用的是哪个版本的函数。
三、 关键字override 和 final
先声明一下:
这两个关键字是c++11才出现的,老版本不支持哦!
(1) override
: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
很多时候,我们需要实现多态,但是由于一时疏忽,将函数名写错了一个字母,或者返回值,参数列表等不同,而导致无法进行虚函数的重写.
class People
{
public:virtual void Have_lunch(){cout << "你需要支付10元的午餐费!" << endl;}
};class Student :public People
{
public:virtual void Have_Lunch()override{cout << "你需要支付8元的午餐费!" << endl;}
};class Teacher :public People
{
public:void Have_lunch()override{cout << "老师,您好,欢迎就餐!已扣除5元餐费." << endl;}
};
有的时候,我们并不像函数成为虚函数,又担心自己不小心满足了虚函数的重写的条件.(其实,博主感觉这种情况很少,也可能是博主目前接触的代码还比较少,没体会到.😂)
(2) final
:修饰虚函数,表示该虚函数不能再被重写.
class People
{
public:virtual void Have_lunch()final{cout << "你需要支付10元的午餐费!" << endl;}
};class Student :public People
{
public:virtual void Have_lunch(){cout << "你需要支付8元的午餐费!" << endl;}
};
对于多态,本文就介绍到这里了,多态的实现原理是什么?菱形继承搭配虚拟继承会不会很头痛?
想要更加深层的理解C++
的多态,下一篇文章,咱们不见不散!