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;消除了漫长的等待…

install mpirun

ubuntu install mpirun : sudo apt install mpich centos install mpirun: download mpich-4.1.2.tar.gz from Downloads | MPICH tar xf mpich-4.1.2.tar.gz cd mpich-4.1.2 ./configure --disable-fortran make;make install

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

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

JS变量、作用域与内存

JavaScript是一种动态类型语言&#xff0c;它的变量声明和作用域有自己独特的规则。在JavaScript中&#xff0c;变量和作用域与内存管理密切相关&#xff0c;因此本文将深入探讨这些主题&#xff0c;以帮助我们更好地理解JavaScript中的变量、作用域和内存。 变量 在JavaScri…

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;嵌入式单片…

如何破解新版 NVIDIA GRID vGPU License Server (DLS)

文章首发在我的个人网站 http://841973620.net/index.php/archives/dls-cracked.html 前排提醒&#xff0c;闲鱼有不法商家修改许可然后出售的&#xff0c;挂这儿给大家避个雷 众嗦粥汁&#xff0c;新版的licserver不再通过验证MAC地址进行授权&#xff0c;而改为上传本地凭…

Oracle数据库中的基本初始化参数

基本初始化参数就是类型为Basic的初始化参数。大多数数据库只需要设置数据库基本初始化参数即可正确有效地运行。Oracle 建议您熟悉基本参数&#xff0c;并且仅在功能文档指示或特殊情况下使用其他参数。 文档1.2 Basic Initialization Parameters中&#xff0c;列出了27个&am…

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…

安卓下Application和Activity的Context获取

情形一&#xff1a;Activity中 通过getApplication()获得Application&#xff0c;其本身也是一个的Context&#xff0c;也可以通过getApplicationContext()获得Application的Context&#xff0c;结果一样。 而this表示当前的Activity对象&#xff0c;其本身也是一个的Context…

(二)linux使用docker容器运行mysql

前言&#xff1a;在上一章安装好linux环境后&#xff0c;开始搭建msyql数据库&#xff0c;之前都是用windows安装mysql的&#xff0c;即使在linux上安装也是用tar解压&#xff0c;然后一堆的配置东西&#xff0c;现在用docker就是爽&#xff0c;几行命令就行 我这里就只放上几…

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…

antv/x6_2.0学习使用(一、安装引入)

安装引入 安装 通过 npm 或 yarn 命令安装 x6 # npm $ npm install antv/x6 --save# yarn $ yarn add antv/x6安装完成之后&#xff0c;使用 import 进行引用 import { Graph } from antv/x6;通过 script 标签引入 可以使用下面三个 CDN 中的任何一个&#xff0c;默认返回…

几代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…

php中实现3DES算法(ECB加密模式PKCS5Padding填充)

讲道理&#xff0c;一提到3DES算法&#xff0c;首先想到的是要和java做交互了 现在3DES已经渐渐被抛弃了&#xff0c;尤其是ECB加密模式使用的更少了&#xff0c;但是没办法&#xff0c;业务需要&#xff0c;合作方用的是他&#xff0c;你头再铁也不能怎样&#xff0c;那就扒扒…

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

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