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.hqwc.cn/news/308195.html

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

相关文章

springboot系列——IDEA创建项目并运行

springboot Spring Boot是为了简化Spring应用程序的开发和部署而产生的。 Spring Boot提供了一种基于约定优于配置的开发模式。它自动配置了Spring应用程序所需的各种组件和依赖&#xff0c;并提供了简单易用的命令行工具来构建和运行应用程序。 Spring Boot还提供了一套开箱…

【FFI】N-API的JS堆对象生命周期管理

N-API的JS堆对象生命周期管理 N-API是Node API的简写&#xff0c;同时也是nodejs的JS VM&#xff08;链&#xff09;接入原生模块.node文件的应用程序二进制接口(i.e. ABI)。借助N-API引入的抽象隔离&#xff0c;升级nodejs运行时&#xff08;虚拟机&#xff09; 【编译】不要求…

Jetson Orin安装riva以及llamaspeak,使用 Riva ASR/TTS 与 Llama 进行实时交谈,大语言模型成功运行笔记

NVIDIA 的综合语音 AI 工具包 RIVA 可以处理这种情况。此外&#xff0c;RIVA 可以构建应用程序&#xff0c;在本地设备&#xff08;如 NVIDIA Jetson&#xff09;上处理所有这些内容。 RIVA 是一个综合性库&#xff0c;包括&#xff1a; 自动语音识别 &#xff08;ASR&#x…

《深入理解JAVA虚拟机笔记》运行时栈帧、方法分派、动态类型

运行时栈帧结构 Java 虚拟机以方法作为最基本的执行单元&#xff0c;“栈帧”&#xff08;Stack Frame&#xff09;则是用于支持虚拟机进行方法调用和方法执行背后的数据结构&#xff0c;它也是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈…

白话机器学习的数学-2-分类

1、设置问题 图片分类&#xff1a;只根据尺寸把它分类为 纵向图像和横向图像。 如果只用一条线将图中白色的点和黑色的点分开&#xff1a; 这次分类的目的就是找到这条线。 2、内积 找到一条线&#xff0c;这是否意味着我们要像学习回归时那样&#xff0c;求出一次函数的斜率…

使用rust读取usb设备ACR122U的nfc卡片id

rust及其高效和安全著称&#xff0c;而且支持跨平台&#xff0c;所以就想使用这个rust开发一个桌面端程序&#xff0c;来读取nfc设备的nfc卡片的id信息&#xff0c;下面就做一个最简单的入门教程吧&#xff0c;也是我写的第三个rust应用。 当你电脑上安装好了rust环境之后&…

人工智能_机器学习083_聚类评价指标_调整兰德系数_算法公式原理解析_手写代码使用兰德系数对聚类结果评分---人工智能工作笔记0123

然后我们再来看一下另一个评价聚类指标的系数,可以看到 兰德系数 上面RI= a+b/C2 ... 首先要知道这里的C,就是实际的类别,就是我们在业务上知道的类别数,然后K表示聚类以后的结果 当然当C==K 一样的时候,说明聚类效果是最好的对吧. a表示在C中被划分为同一类,也就是,比如一组…

【洛谷学习自留】p7621 超市购物

2023/12/29 解题思路&#xff1a; 简单的计算&#xff0c;难度主要集中在格式化输出和四舍五入的问题上。 1.建立一个计数器&#xff0c;for循环遍历单价和数量的乘积&#xff0c;存入计数器。 2.计算计数器的最终值乘以0.85h后的结果&#xff0c;为了保证四舍五入正确&…

什么是uniapp?用uniapp开发好不好用?

随着移动应用市场的持续发展&#xff0c;开发者们面临着不断增长的需求和多样化的平台选择。在这个背景下&#xff0c;UniApp 应运而生&#xff0c;成为一种跨平台开发框架&#xff0c;为开发者提供了一种高效、简便的方式来开发移动应用程序。利用 UniApp 开发应用程序可以节省…

基于ElementUI二次封装el-table与el-pagination分页组件[实际项目使用]

效果&#xff1a; 二次封装el-table组件 <template><div><!-- showHeader:是否显示头部size:表格的大小height:表格的高度isStripe:表格是否为斑马纹类型tableData:表格数据源isBorder:是否表格边框handleSelectionChange:行选中&#xff0c;多选内容发生变化回…

thinkphp+vue_mysql汽车租赁管理系统1ma2x

运行环境:phpstudy/wamp/xammp等 开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp5 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat/phpmyadmin 课题主要分为三大模块&#xff1a;即管理员模块、用户模块…

【js自定义鼠标样式】【js自定义鼠标动画】

文章目录 前言一、效果图二、实现步骤1. 去除原有鼠标样式2. 自定义鼠标样式3. 使用 总结 前言 自定义鼠标形状&#xff0c;自定义鼠标的动画&#xff0c;可以让我们的页面更加有设计感。 当前需求&#xff1a;吧鼠标自定义成一个正方形&#xff0c;鼠标的效果有&#xff1a;和…