06-C++ 类和对象-多态

类与对象

多态

1. 简介

一个事物的多种形态,简称多态。

  • 物的多态

    同一个人在不同人面前,角色不同

    如:

    1. 在父母面前
    2. 在对象面前
    3. 在朋友面前
    4. 在同事面前
  • 事的多态

    同一种事情,在不同情况下展现不同

    如:

    1. 吃饭

      • 中国人 筷子 熟食
      • 美国人 刀叉 7分熟
      • 印度人 手 咖喱饭
    2. 睡觉

      • 中国人 床上
      • 日本人 地上

      平躺

      侧卧

      趴着

2. 上行与下行

2.1 上行

子类 转 父类

语法:

父类名 *父类对象指针 = 子类对象指针;
或
父类名& 父类对象名 = 子类对象;

注意:

无风险,无需强转。

2.2 下行

父类 转 子类

语法:

子类名 *子类对象指针 = (子类名 *)父类对象指针;
或
子类名& 子类对象 = (子类名&) 父类对象;

注意:

有风险,需强转。

2.3 示例
#include <iostream>
using namespace std;class Anim{
public:int a;Anim(){}Anim(int a):a(a){}
};class Dog:public Anim{
public:int d;Dog(){}Dog(int a, int d):Anim(a),d(d){}
};
class Cat:public Anim{
public:int c;
};int main(int argc, char *argv[])
{//上行//将子类对象的地址赋值给父类对象的引用或指针Dog d1(10, 100);Anim& a1 = d1;//赋值给指针,记得取地址,不然报错Anim* a2 = &d1;cout << "d1.d=" << d1.d << endl;    //d1.d=100cout << "a1.a=" << a1.a << endl;    //a1.a=10//子类转父类后, 父类对象不能使用子类所特有的属性
//    cout << "a1.d=" << a1.d << endl;      //报错//下行Dog d2 = (Dog &)a1;Dog *d3 = (Dog *)a2;cout << "d2.d=" << d2.d << endl;    //d2.d=100//父类转换子类后,子类可以使用父类的cout << "d2.a=" << d2.a << endl;    //d2.a=10//下行父转子Cat& c1 = (Cat &)a1;cout << "c1.a=" << c1.a << endl;    //c1.a=10//d1的内存中没有c,所以c1.c 的值为 d1中d的值cout << "c1.c=" << c1.c << endl;    //c1.c=100return 0;
}

在这里插入图片描述

分析:

  • Dog继承了 Anim,所以d1对象中 有a 也有 d;
  • 父转子,c1也指向 d1(可能会报错,编译器优化可能也不报错)

3. 重写

继承关系中,返回值类型相同,函数名形同,形参列表相同,函数体不同 。

重载

同一作用域下,函数名相同,形参列表不同

重定义

继承关系中,函数名相同即可。一旦重定义后,子类会将父类的函数覆盖掉

4. c++多态分类

多态分为:

  • 物的多态(上行、下行)

  • 事的多态(静态多态,动态多态)

4.1 静态多态(早绑定,静态联编)

概念: 在编译阶段 就确定函数的入口地址
又名: 静态联编,早绑定
如: 函数重载,运算符重载,重定义等

函数在代码区,函数名就是这个函数在代码区的地址,这个地址就是这个函数的地址

4.2 动态多态(晚绑定,动态联编)

概念: 在运行阶段确定程序入口地址
又名: 动态联编,晚绑定
如: 虚函数,重写

5. 引入

要求:设计一个函数,根据传入的对象调用重写的方式。

5.1 示例

需求:

小明开了一个宠物医院,可以给狗看病,可以给猫看病可以给猪看病张女士带着他家的狗旺财去找小明给狗看病李女士带着他家的猫布丁去找小明给猫看病王先生带着他家的猪佩奇去找小明给猪看病分析:对象小明张女士李女士王先生旺财布丁佩奇类动物类属性:姓名狗类猫类猪类人类属性:动物get与set宠物医生类看病

代码:

#include <iostream>
#include <cstring>
using namespace std;class Anim{
private:char name[50];
public:Anim(){}Anim(char *name){strcpy(this->name,name);}char* getName(){return name;}void setName(char *name){strcpy(this->name,name);}void call(){cout << "动物叫" << endl;}
};
class Dog:public Anim
{
public:Dog(){}Dog(char *name):Anim(name){}//重写父类call方法void call(){cout << this->getName() << ":汪汪汪" << endl;}
};
class Cat:public Anim
{
public:Cat(){}Cat(char *name):Anim(name){}//重写父类call方法void call(){cout << this->getName() << ":喵喵喵" << endl;}
};
class Pig:public Anim
{
public:Pig(){}Pig(char *name):Anim(name){}//重写父类call方法void call(){cout << this->getName() << ":哼哼哼" << endl;}
};
class Person:public Anim
{
private:Anim* anim;
public:Person(){anim = NULL;}Person(char *name):Anim(name){}Person(char *name,Anim *anim):Anim(name),anim(anim){}~Person(){if(anim != NULL){delete anim;anim = NULL;}}void setAnim(Anim* anim){this->anim = anim;}Anim* getAnim(){return anim;}//重写父类call方法void call(){cout << this->getName() << ":哇哇哇" << endl;}
};
class AnimDoctor:public Person{
public:AnimDoctor(){}AnimDoctor(char *name):Person(name){}AnimDoctor(char *name,Anim* anim):Person(name,anim){}void cb(Anim* anim){cout << this->getName() << "给" << anim->getName() << "看病" << endl;anim->call();}
};
int main(int argc, char *argv[])
{Dog * dog = new Dog("旺财");Cat * cat = new Cat("布丁");Pig * pig = new Pig("佩奇");Person *p1 = new Person("张女士",dog);Person *p2 = new Person("李女士",cat);Person *p3 = new Person("王先生",pig);AnimDoctor * doctor = new AnimDoctor("小明");doctor->cb(p1->getAnim());return 0;
}//小明给旺财看病
//动物叫
//小明给布丁看病
//动物叫
//小明给佩奇看病
//动物叫
5.2 问题

父类有个函数 void call(){},要求是子类继承并重写 父类 该方法,每个子类 动物在看病时,都会叫 “狗:汪汪汪,猫:喵喵喵…” ;

但是,此时现状是,每次打印出来的都是 父类 void call(){} 函数中 的 “动物叫”(子传父 上行之后调用的依旧是父类的call函数),而我们需要的是 子传父之后,调用的是每个子类特有的方法。

所以需要引入 虚函数

6. 虚函数

概念:virtual 修饰的成员函数,就是虚函数

语法:

virtual 返回值类型 函数名(形参列表)
{函数体
}

注意:

  • 子类在继承父类时,会生成 虚函数指针

对比如下:以上边动物看病为例

  • 不是虚函数

在这里插入图片描述

在这里插入图片描述

  • 虚函数:下面第2幅图可以看出,子类继承了父类的 name 和 虚函数指针 vfptr ,此时指针指向的是 子类自己的 函数 Dog::call

在这里插入图片描述

在这里插入图片描述

特点:

子类转换为父类 后:

  • 使用该父类调用 使用virtual修饰的函数,调用的是子类重写后的函数

  • 使用该父类调用普通函数,调用的是父类的该函数

6.1 上边示例修改
#include <iostream>
using namespace std;
#include <cstring>
class Anim{
private:char name[50];
public:Anim(){}Anim(char *name){strcpy(this->name,name);}char* getName(){return name;}void setName(char *name){strcpy(this->name,name);}virtual void call(){cout << "动物叫" << endl;}
};
class Dog:public Anim
{
public:Dog(){}Dog(char *name):Anim(name){}virtual void call(){cout << this->getName() << ":汪汪汪" << endl;}
};
class Cat:public Anim
{
public:Cat(){}Cat(char *name):Anim(name){}void call(){cout << this->getName() << ":喵喵喵" << endl;}
};
class Pig:public Anim
{
public:Pig(){}Pig(char *name):Anim(name){}void call(){cout << this->getName() << ":哼哼哼" << endl;}
};
class Person:public Anim
{
private:Anim* anim;
public:Person(){anim = NULL;}Person(char *name):Anim(name){}Person(char *name,Anim *anim):Anim(name),anim(anim){}~Person(){if(anim != NULL){delete anim;anim = NULL;}}void setAnim(Anim* anim){this->anim = anim;}Anim* getAnim(){return anim;}void call(){cout << this->getName() << ":哇哇哇" << endl;}
};
class AnimDoctor:public Person{
public:AnimDoctor(){}AnimDoctor(char *name):Person(name){}AnimDoctor(char *name,Anim* anim):Person(name,anim){}void cb(Anim* anim){cout << this->getName() << "给" << anim->getName() << "看病" << endl;anim->call();}
};
int main(int argc, char *argv[])
{Dog * dog = new Dog("旺财");Cat * cat = new Cat("布丁");Pig * pig = new Pig("佩奇");Person *p1 = new Person("张女士",dog);Person *p2 = new Person("李女士",cat);Person *p3 = new Person("王先生",pig);AnimDoctor * doctor = new AnimDoctor("小明");doctor->cb(p1->getAnim());doctor->cb(p2->getAnim());doctor->cb(p3->getAnim());/*当子类转换为父类后使用该父类调用使用virtual修饰的函数,调用的是子类重写后的函数使用该父类调用普通函数,调用的是父类的该函数*/// 虚函数所在的类,依据可以直接创建对象Anim a;return 0;
}
//小明给旺财看病
//旺财:汪汪汪
//小明给布丁看病
//布丁:喵喵喵
//小明给佩奇看病
//佩奇:哼哼哼
6.2 动态绑定的条件(重要)
有继承,子类重写父类的虚函数,父类指针或引用指向子类空间(上行)。父类指针或引用才能调用子类重写的虚函数。错误演示:B b;//此时会调用父类的拷贝构造,会产生一个新的父类对象,该 父类对象a 与 子类对象b 是两个独立空间//所以此时使用a对象调用test01依据会执行父类的test01函数A a = b; //拷贝构造a.test01();
6.3 动态绑定原理(机制)(重要)
  • 父类有虚函数,产生的 虚函数指针 指向 虚函数表,表中记录的是 父类的虚函数地址
  • 如果子类继承 父类,那么子类会继承父类的虚函数指针以及虚函数表。
  • 如果子类 重写父类的虚函数,会将将虚函数表纪录的入口地址修改成子类重写的函数入口地址。
  • 这时 父类指针指向子类空间,父类指针调用虚函数就 间接 调用子类重写的虚函数。

7. 纯虚函数

概念:父类的虚函数没有函数体

语法:

virtual 返回值类型 函数名(形参列表) = 0;

注意:

  • 纯虚函数所在的类不能 直接 创建对象,这种类被称为抽象类
  • 子类继承与抽象类,要么重写父类提供的所有纯虚函数,要么自己也是抽象类

示例:

#include <iostream>
#include <cstring>
using namespace std;
//纯虚函数所在的类称为抽象类
/*特点:*  1,抽象类不能直接创建对象*  2,子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类*/
class Anim{
private:char name[50];
public:Anim(){}Anim(char *name){strcpy(this->name,name);}char* getName(){return name;}void setName(char *name){strcpy(this->name,name);}//纯虚函数virtual void call() = 0;virtual void sleep() = 0;
};
class Dog:public Anim{
public:Dog(){}Dog(char *name):Anim(name){}void call(){cout << "汪汪汪" << endl;}
};
class Cat:public Anim{
public:Cat(){}Cat(char *name):Anim(name){}void call(){cout << "喵喵喵" << endl;}void sleep(){cout << "在猫窝睡" << endl;}
};
int main(int argc, char *argv[])
{//有纯虚函数所在的类为抽象类,抽象类不能直接创建对象//Anim anim//子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类//Dog类只重写了call纯虚函数,但是没有重写sleep纯虚函数//所以Dog类也是抽象类,所以不能直接创建对象//Dog dog;//Cat类重写了Anim类所有的纯虚函数,所以Cat类不是抽象类Cat cat;Anim& anim = cat;cout << "Hello World!" << endl;return 0;
}

8. 虚析构造

8.1 问题引入
#include <iostream>using namespace std;
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}~Anim(){cout << "父类析构函数" << endl;}
};class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}
//父类构造函数
//子类构造函数
//父类析构函数

问题:没有调用 子类析构函数

8.2 解决方案

将父类的析构函数 设置成 虚析构

虚析构函数是为了解决:基类的指针指向派生类对象,并用基类的指针删除派生类对象。

语法:

virtual ~析构函数()
{}

示例:

#include <iostream>using namespace std;
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}//虚析构virtual ~Anim(){cout << "父类析构函数" << endl;}
};class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}
//父类构造函数
//子类构造函数
//子类析构函数
//父类析构函数

9. 纯虚析构(了解)

效果等同于 虚析构

语法:

virtual 析构函数名() = 0;

注意:需要在类外实现析构函数

示例:

#include <iostream>using namespace std;
//class Anim{
//public:
//    Anim()
//    {
//        cout << "父类构造函数" << endl;
//    }
//    //虚析构
//    virtual ~Anim(){
//         cout << "父类析构函数" << endl;
//    }
//};
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}//纯虚析构//类中定义virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}
virtual 析构函数名() = 0;

注意:需要在类外实现析构函数

示例:

#include <iostream>using namespace std;
//class Anim{
//public:
//    Anim()
//    {
//        cout << "父类构造函数" << endl;
//    }
//    //虚析构
//    virtual ~Anim(){
//         cout << "父类析构函数" << endl;
//    }
//};
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}//纯虚析构//类中定义virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/584398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8天狂收6100颗星,可商用!东京工业、麻省理工等开源Stream Diffusion

加州大学伯克利分校、东洋大学、东京工业大学、麻省理工学院和筑波大学等研究人员&#xff0c;联合开源了一款创新性实时交互图像生成框架——Stream Diffusion。 Stream Diffusion的技术创新点在于&#xff0c;将传统的顺序去噪变成流批处理去噪&#xff0c;消除了漫长的等待…

vue项目hdr格式文件放在assets下rgbeloader.load获取不到问题解决

如下图 我再App.vue组件中这样写 艾特符号定位 告诉系统 要src下的assets下的xhdr下的xidis.hdr 但是运行项目 他会告诉你找不到这个资源 我们改一下 我们组件时 App.vue 与assets同在 src目录下 用 ./去找 这样也是找不到的 我们需要将它放在静态资源包public下 public路…

CDSP考取的价值:成为数据安全认证专家的好处

哈喽IT的朋友们&#x1f44b;&#xff0c;今天想和大家聊聊一个超级有用的专业认证&#xff1a;CDSP&#xff0c;也就是数据安全认证专家。如果你在数据安全领域或者对这方面感兴趣&#xff0c;这个认证绝对值得你去考取哦&#xff01; 1.&#x1f393;提升专业性&#xff1a;获…

MCEWMDRMNDBootstrap.dll文件丢失,软件游戏无法启动,怎样下载修复

不少小伙伴反馈&#xff0c;在打开某些游戏或软件的时候&#xff0c;Windows会提示“MCEWMDRMNDBootstrap.dll文件丢失&#xff0c;软件无法启动”&#xff0c;不知道应该怎样办&#xff1f; 首先&#xff0c;我们先来了解“MCEWMDRMNDBootstrap.dll文件”是什么&#xff1f; …

单片机开发从小工到专家

有道无术&#xff0c;术尚可求&#xff1b;有术无道&#xff0c;止于术 背景 向单片机嵌入式开发小伙伴推荐了几本书&#xff0c;阅读量破10 1. 适用范围 2. 书籍推荐 书籍推荐 3. 大师介绍 大师介绍 4. 大师书籍编写逻辑 25年大师出版的关于&#xff1a;嵌入式单片…

wireshark access/trunk/hybrid报文分析

1&#xff0c;access接口 发送带vlan的报文 wireshark交换机配置 [Huawei-GigabitEthernet0/0/1] [Huawei-GigabitEthernet0/0/1]port link-type access [Huawei-GigabitEthernet0/0/1]port default vlan 100 [Huawei-GigabitEthernet0/0/2]port link-type access [Huawei-Gig…

Dockerfile - 基于 SpringBoot 项目自定义镜像(项目上线全过程)

目录 一、Dockerfile 自定义项目镜像 1.1、创建 SpringBoot 项目并编写 1.2、打包项目&#xff08;jar&#xff09; 1.3、编写 Dockerfile 文件&#xff0c;构建镜像 1.4、运行镜像并测试 一、Dockerfile 自定义项目镜像 1.1、创建 SpringBoot 项目并编写 a&#xff09;简…

SonarQube安装踩坑记录

如果用java1.8和mysql&#xff0c;则SonarQube版本不能超过7.8&#xff0c;看这里。 SonarQube7.8安装包地址&#xff1a; https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-7.8.zip 安装步骤&#xff1a; 1、下载SonarQube安装包 wget https://binari…

几代WiFi有什么差异,它们有什么区别

最典型的差异指标&#xff1a;单流传输速率 第一代 基于的标准&#xff1a; 802.11 使用频率&#xff1a;2.4GHz 单流最大传输速率&#xff1a;2Mbit/s 第二代 基于的标准&#xff1a; 802.11b 使用频率&#xff1a;2.4GHz 单流最大传输速率&#xff1a;11Mbit/s 第三代 …

Anolis安装Jdk保姆级教学

前言 欢迎来到本博客&#xff0c;我们将带领你完成在Anolis操作系统上安装Java Development Kit&#xff08;JDK&#xff09;的详细过程。Anolis操作系统是一款基于Linux的轻量级操作系统&#xff0c;专为容器和云原生应用而设计。在Anolis上安装JDK将为你提供一个稳定、高效的…

洛谷:集合与前缀和

1.亲戚(并查集) #include<iostream> using namespace std; int n,m,p; int m1,m2,p1,p2; int f[5005];int find(int x)//查询根节点&#xff0c;根节点的标志是根节点的父节点是自己 {if(f[x]!x)f[x]find(f[x]);//路径压缩&#xff0c;父节点变为根节点,方便下次询问retu…

Python 简易图形界面库easygui 对话框大全(续)

目录 EasyGUI库 主要特点 使用场景 对话框样式 10. 文件打开框 fileopenbox 11. 文件保存框 filesavebox 12. 目录打开框 diropenbox 13. 索引对话框 indexbox 14. 例外报告框 exceptionbox 15. 代码文本框 codebox 16. 密码输入框 passwordbox 17. 多重文本框 mul…

MySql数据库(3)——DML

一、DML DML&#xff08;Data Manipulation Language&#xff09;数据操作语言&#xff0c;常用语句&#xff1a; insert&#xff1a;输入delete&#xff1a;删除update&#xff1a;修改 插入数据 方式一&#xff1a;insert into 表名 &#xff08;列1&#xff0c;列2……&a…

jmeter之beanshell使用:常用变量汇总

1.变量--日期 使用场景&#xff1a;当入参日期是变量&#xff0c;取当前日期 使用如下&#xff1a; &#xff08;1&#xff09;当前日期 import java.text.SimpleDateFormat; import java.util.Date;// 创建 SimpleDateFormat 对象并指定日期格式 SimpleDateFormat dateFor…

价格成谜,小米汽车到底要卖给谁?

文&#xff5c;刘俊宏 编&#xff5c;王一粟 “不要再讲9.9万了&#xff0c;不可能的&#xff0c;也不要再讲14.9万&#xff0c;小米SU7是有理由的贵”&#xff0c;小米集团创始人、董事长雷军在12月28日的小米汽车技术发布会上&#xff0c;把价格作为最大的悬念留给了市场。…

【Python排序算法系列】—— 选择排序

​ &#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 选择排序 过程演示&#xff1a; 选择排序实现代码&#xff1a; 分析选择排序&#xff1a…

Grafana无法发送告警消息的飞书webhook(机器人)

1.问题描述 Grafana无法向飞书机器人发送报警消息&#xff0c;实测使用Grafana自带的webhook也不好使&#xff0c;对于用飞书办公的程序猿非常不便&#xff0c;后来发现一个报警神器&#xff0c;开源免费&#xff0c;关键是好用 PrometheusAlert 2.PrometheusAlert安装 Prom…

【办公软件】Excel双坐标轴图表

在工作中整理测试数据&#xff0c;往往需要一个图表展示两个差异较大的指标。比如共有三个数据&#xff0c;其中两个是要进行对比的温度值&#xff0c;另一个指标是两个温度的差值&#xff0c;这个差值可能很小。 举个实际的例子&#xff1a;数据如下所示&#xff0c;NTC检测温…

openGauss学习笔记-178 openGauss 数据库运维-逻辑复制-逻辑解码-使用SQL函数接口进行逻辑解码

文章目录 openGauss学习笔记-178 openGauss 数据库运维-逻辑复制-逻辑解码-使用SQL函数接口进行逻辑解码178.1 前提条件178.2 操作步骤 openGauss学习笔记-178 openGauss 数据库运维-逻辑复制-逻辑解码-使用SQL函数接口进行逻辑解码 openGauss可以通过调用SQL函数&#xff0c;…

医院绩效考核系统源码,java源码,商业级医院绩效核算系统源码

医院绩效定义&#xff1a; “医院工作量绩效方案”是一套以工作量&#xff08;RBRVS&#xff0c;相对价值比率&#xff09;为核算基础&#xff0c;以工作岗位、技术含量、风险程度、服务数量等业绩为主要依据&#xff0c;以工作效率和效益、工作质量、患者满意度等指标为综合考…