【C++】多态 — 认识多态 + 多态的条件及其性质(上篇)
创始人
2024-04-01 11:59:15

文章目录

  • 📖 前言
  • 1. 多态的概念
  • 2. 多态的定义及实现
    • 2.1 虚函数:
    • 2.2 虚函数的重写(覆盖):
      • 2.2 - 1 多态的两个条件(重点)
  • 3. 虚函数重写的两个例外
    • 3.1 协变 - 基类与派生类虚函数返回值类型不同:
      • 3.1 - 1 一道非常考验基础知识的笔试题
    • 3.2 析构函数的重写 - 基类与派生类析构函数的名字不同:

📖 前言

C++是一门面向对象的语言,其三大特性,封装、继承、多态。
本文将介绍来好好讲讲多态…


1. 多态的概念

多态的概念:

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。


2. 多态的定义及实现

2.1 虚函数:

虚函数: 即被 virtual 修饰的类成员函数称为虚函数。

class Person
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

2.2 虚函数的重写(覆盖):

虚函数的重写(覆盖):

  • 派生类中有一个跟基类完全相同的虚函数
  • 即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同
  • 称子类的虚函数(重写)了基类的虚函数,也叫(覆盖)

下面我们实现一个简单的买票系统 —— 要求实现不同的对象做同一件事产生不同的状态:

//多态只用的样例:
class Person
{
public:Person(const char* name):_name(name){}//虚函数virtual void BuyTicket() { cout << _name << " Person: 买票-全价 100¥" << endl; }protected:string _name;//int _id;
};class Student : public Person
{
public:Student(const char* name):Person(name){}//虚函数 + 函数名/参数/返回值 -> 重写/覆盖virtual void BuyTicket() { cout << _name << " Student: 买票-半价 50¥" << endl; }
};class Soldier : public Person
{
public:Soldier(const char* name):Person(name){}//虚函数 + 函数名/参数/返回值 -> 重写/覆盖virtual void BuyTicket() { cout << _name << " Soldier: 优先买预留票-88折 100¥" << endl; }
};void Pay(Person* ptr)
{ptr->BuyTicket();delete ptr;
}//赋值兼容的转换,父类指针可以指向父类对象,也可以指向子类对象
void Pay(Person& ptr)
{ptr.BuyTicket();
}全部都去调用父类去了 -- 不构成多态
//void Pay(Person ptr)
//{
//	ptr.BuyTicket();
//}int main()
{int option = 0;cout << "=========================================" << endl;do{cout << "请选择身份:";cout << "1、普通人 2、学生 3、军人" << endl;cin >> option;cout << "请输入名字:";string name;cin >> name;//switch case语句里面,是不能支持定义对象的,要加一个域{}//加完域之后就是局部域了switch (option){case 1:{Person p(name.c_str());Pay(p);break;}case 2:{Student s(name.c_str());Pay(s);break;}case 3:{Soldier s(name.c_str());Pay(s);break;}default:cout << "输入错误,请从新输入" << endl;break;}cout << "=========================================" << endl;} while (option != -1);return 0;
}

上述代码总结:

  • 多态是同样是买票,不同的人买票,结果不一样
  • 父类的指针,父类的引用,可以指向子类的对象,也可以指向父类的对象

在这里插入图片描述

2.2 - 1 多态的两个条件(重点)

  • 必须是父类的指针或者引用去调用虚函数
  • 子类虚函数重写父类的虚函数(重写:三同 【函数名/参数/返回值】 + 虚函数)

1.为什么一定是父类的指针和引用去调用虚函数,父类的对象可以吗?

在这里插入图片描述

先看运行结果:

在这里插入图片描述

  • 因为都调用到父类对象去了 —— 不构成多态
  • 原理要到多态的原理中才能理清楚

2.如果有一个条件不满足多态的话(当参数不满足相同时):

在这里插入图片描述
我们来看运行结果:

在这里插入图片描述

  • 我们看到结果是有问题的,并没有达到我们想要的情况,军人的虚函数不符合重写的规则
  • 到讲解多态的原理时,我们再来深入学习
  • 现在只需要记住多态的两个条件(重点)

3. 虚函数重写的两个例外

3.1 协变 - 基类与派生类虚函数返回值类型不同:

1.协变的概念:

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

  • 虚函数重写对返回值的要求有个例外,叫作:协变
  • 协变的返回值类型也不是随便的,必须是(父子关系)的指针和引用

2.父子关系的指针:

class A
{};class B : public A
{};class Person
{
public:virtual A* f(){cout << "virtual A* Person::f()" << endl;return nullptr;}
};class Student : public Person
{
public:virtual B* f(){cout << "virtual B* Student::f()" << endl;return nullptr;}
};int main()
{Person p;Student s;Person* ptr = &p;ptr->f();ptr = &s;ptr->f();return 0;
}

在这里插入图片描述

3.父子关系的引用:

在这里插入图片描述

在这里插入图片描述
4.父子关系的对象:

在这里插入图片描述
在这里插入图片描述
可见父子关系的对象是不行的。

C++在这里搞麻烦了,意义不是很大。

5.如果同为父类:

在这里插入图片描述
在这里插入图片描述

这里则是继承中的隐藏。


补充:

  • 子类的虚函数没有写virtual,f依旧是虚函数
  • 因为先继承了父类函数接口的声明
  • 重写的是父类虚函数的实现
  • 所以父类有virtual的属性子类也就有了
  • 这样写不太规范

建议:

  • 最好不要协变或者子类不加virtual
  • 我们自己写的时候子类虚函数也写上virtual

3.1 - 1 一道非常考验基础知识的笔试题

1.以下程序输出结果是什么?

class A
{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }virtual void test() { func(); }
};class B : public A
{
public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};int main()
{B* p = new B;p->test();return 0;
}

运行结果:
在这里插入图片描述

重写其实是一种接口继承。

在这里插入图片描述

  • 这样写其实增加了学习者的理解难度。

2.看下面代码的运行结果是什么?
在这里插入图片描述
运行结果:
在这里插入图片描述
这里就是类成员的普通调用,并不是多态的调用。

3.再来看一组:
在这里插入图片描述
这才是多态的调用,时刻记住多态的两个条件!

多态:拿一个类的指针去调用另一个类的函数。


3.2 析构函数的重写 - 基类与派生类析构函数的名字不同:

  • 重写又叫做覆盖
  • 隐藏又叫做重定义

1.析构函数不构成多态的情况:

在这里插入图片描述
在这里插入图片描述
普通调用看的指针类型,指针类型是Person * ,就去调用Person的析构函数去了

  • 如果不构成多态这是一个普通调用
  • 普通调用都是看调用变量或者指针的类型

问题:

  • 这里调用就会出现调用不到子类的析构,会存在内存泄露的风险。

2.解决办法 - 多态:

class Person 
{
public:virtual ~Person() { cout << "~Person()" << endl; }
};class Student : public Person 
{
public:virtual ~Student() { cout << "~Student()" << endl; }
};int main()
{Person p;Student s;return 0;
}

(1)对于普通对象没有影响:

在这里插入图片描述

  • 子类对象析构的时候是先子后父,初始化是先父后子
  • 调用子类的析构函数会自动调用父类的析构函数

析构函数默认是隐藏关系 – 都被处理成destructor

  • 如果Person析构函数加了virtual,关系就变了
  • 加上virtual之后就从隐藏关系变成了:重写关系 – 隐藏/重定义->重写/覆盖

(2)但是指针的切片调用就会有不同,触发了多态:

在这里插入图片描述
在这里插入图片描述
建议:

  • 如果设计的一个类,可能作为基类。
  • 其析构函数最好定义为虚函数。

相关内容

热门资讯

长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...