C++面向对象程序设计-北京大学-郭炜【课程笔记(八)】

C++面向对象程序设计-北京大学-郭炜【课程笔记(八)】

  • 1、虚函数和多态的基本概念
    • `1.1、虚函数`
    • 1.2、多态
      • `多态`的表现形式一
      • `多态`的表现形式二
  • 2、多态实例:魔法门之英雄无敌
    • 2.1、**非多态的实现方法:**
    • 2.2、**多态的实现方法**
  • 3、多态实例:几何形体程序
    • 3.1、qsort函数的介绍
    • 3.2、几何形体程序
    • 3.2、铭记口诀
    • 3.3、多态例题2:
    • 3.3、多态实例3:与构造函数
  • 4、多态的实现原理
  • 5、虚析构函数、纯虚函数和抽象类
    • 5.1、虚析构函数
    • 5.2、纯虚函数和抽象类
  • 5.3、虚函数和纯虚函数的区别

开始课程:P28 1_1. 虚函数和多态的基本概念
课程链接:程序设计与算法(三)C++面向对象程序设计 北京大学 郭炜
课程PPT:github提供的对应课程PPT

1、虚函数和多态的基本概念

1.1、虚函数

  • 在类的定义中,前面有virtual关键字的成员函数就是虚函数。
class base
{virtual int get();
};
int base::get() {}
  • virtual关键字只用在类定义里的函数声明中,写函数体时不用。
    • 什么是函数体:函数体指的是函数定义中包含的代码块,用于实现函数的功能。 在函数定义中,通常会指定函数的名称、参数列表和返回值类型,而函数体则是具体实现函数功能的地方。
  • 构造函数和静态成员函数不能是虚函数

1.2、多态

多态的表现形式一

  • 派生类的指针可以赋给基类指针。
  • 通过基类指针调用基类和派生类中的同名虚函数时:
    • (1)、若该指针指向一个基类的对象,那么被调用是基类的虚函数;
    • (2)、若该指针指向一个派生类的对象,那么被调用的是派生类的许函数。
      以上这种机制就叫做“多态”。
#include <iostream>
using namespace std;class CBase
{public:virtual void SomeVirtualFunction(){cout << "基类" << endl;}
};class CDerived:public CBase
{public:virtual void SomeVirtualFunction(){cout << "派生类" << endl;}
};int main()
{CDerived ODerived;CBase * p = & ODerived;// 调用哪个虚函数取决于p指向哪种类型的对象// 即派生类CDerived的对象p -> SomeVirtualFunction();  return 0;
}OUT:
beida_lesson % g++ 25.cpp -o 25
beida_lesson % ./25 
派生类

多态的表现形式二

  • 派生类的对象可以赋值给基类引用
  • 通过基类引用调用基类和派生类中的同名虚函数时:
    • (1)、若该引用引用的是一个基类的对象,那么被调用时基类的虚函数
    • (2)、若该引用引用的是体格派生类的对象,那么被调用的是派生类的许函数。
      以上这种机制就叫做“多态”。
#include <iostream>
using namespace std;class CBase
{public:virtual void SomeVirtualFunction(){cout << "基类" << endl;}
};class CDerived:public CBase
{public:virtual void SomeVirtualFunction(){cout << "派生类" << endl;}
};int main()
{CDerived ODerived;// 派生类的指针可以赋给基类指针// CBase * p = & ODerived;// p -> SomeVirtualFunction(); //2、派生类的对象可以赋值给基类引用CBase & r = ODerived;r.SomeVirtualFunction();// 调用哪个虚函数取决于p\r指向哪种类型的对象// 即派生类CDerived的对象 return 0;
}
OUT:
beida_lesson % g++ 25.cpp -o 25
beida_lesson % ./25 
派生类

例:
在这里插入图片描述


int main()
{A a; B b; E e; D d;  A *pa = &a; B *pb = &b;D *pd = &d; E *pe = &e;pa -> Print();   // a.Print()被调用,输出:A::Printpa = pb;pa -> Print();   // b.Print()被调用,输出:B::Printpa = pd;pa -> Print();   // d.Print()被调用,输出:D::Printpa = pe;pa -> Print();   // e.Print()被调用,输出:E::Printreturn 0;
}

2、多态实例:魔法门之英雄无敌

在这里插入图片描述
请添加图片描述请添加图片描述请添加图片描述请添加图片描述

2.1、非多态的实现方法:

#include <iostream>
using namespace std;class CCreature
{protected:int nPower;  // 代表攻击力int nLifeValue;  // 代表生命值2
};class CDragon:public CCreature
{public:void Attack(CWolf * pWolf){// 表现攻击动作的代码pWolf -> Hurted(nPower);pWolf -> FightBack(this);   // 表示Attack的对象:攻击的发起者}void Attack(CGhost * pGhost){// 表现攻击动作的代码pGhost -> Hurted(nPower);pGhost -> FightBack(this);}void Hurted(int nPower){// 表示受伤动作的代码nLifeValue -= nPower;}void FightBack(CWolf * pWolf){// 表示反击动作的代码pWolf -> Hurted(nPower / 2);}void FightBack(CGhost * pGhost){pGhost -> Hurted(nPower / 2);}
};

有n种怪物,CDragon类中就会有nAttack成员函数,以及nFightBack成员函数。对于其他类特使如此。

在这里插入图片描述

因为每个动物的攻击和反击方式不一样,所以不能直接在基类CCreature中定义Attack和FightBack成员函数。每一个动物要想攻击另一个动物必须含有攻击这个动物的成员函数,所以一旦添加新的怪物那么就需要在原有每个怪物类中添加Attack和FightBack两个成员函数。

2.2、多态的实现方法

请添加图片描述
请添加图片描述
请添加图片描述
在这里插入图片描述

3、多态实例:几何形体程序

3.1、qsort函数的介绍

qsort函数的声明

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
  • base – 指向要排序的数组的第一个元素的指针。
  • nitems – 由 base 指向的数组中元素的个数。
  • size – 数组中每个元素的大小,以字节为单位。
  • *compar:回调函数的函数指针,需要用户自己实现回调函数

3.2、几何形体程序

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

#include <iostream>
#include <stdlib.h>
#include <math.h>using namespace std;class CShape
{public:virtual double Area() = 0; // 纯虚函数virtual void PrintInfo() = 0;
};class CRectangle:public CShape
{public:int w,h;virtual double Area();virtual void PrintInfo();
};class CCircle:public CShape
{public:int r;virtual double Area();virtual void PrintInfo();
};class CTriangle:public CShape
{public:int a,b,c;virtual double Area();virtual void PrintInfo();
};double CRectangle::Area()
{return w*h;
}void CRectangle::PrintInfo()
{cout << "Rectangle" << Area() << endl;
}double CCircle::Area()
{return 3.14*r*r;
}void CCircle::PrintInfo()
{cout << "Rectangle" << Area() << endl;
}double CTriangle::Area()
{double p = (a + b + c)/2.0;return sqrt(p*(p-a)*(p-b)*(p-c));
}void CTriangle::PrintInfo()
{cout << "Traingle" << Area() << endl;
}CShape * pShape[100]; // 基类的指针数组
int MyCompare(const void * s1, const void * s2)
{double a1, a2;   CShape * * p1;  // s1,s2是void *,不可写“* s1”来取得s1指向的内容CShape * * p2;  p1 = (CShape * *)s1;   // s1,s2指向pShape数组中的元素,数组元素的类型是CShapep2 = (CShape * *)s2;   // 故p1,p2都是指向指针的指针,类型为CShape **a1 = (*p1)->Area();    // * p1的类型是Cshape *,是基类指针,故此句为多态a2 = (*p2)->Area();if(a1<a2)return -1;else if(a2 < a1)return 1;elsereturn 0;
}int main()
{int i; int n;CRectangle * pr; CCircle * pc; CTriangle * pt;cout << "请输入几何形体的数量:" << endl;cin >> n;  // 输入几何形体的数量for(i=0; i<n; i++){char c;cout << "请输入几何形体的种类R/C/T:" << endl;cin >> c;  // 输入几何形体的种类switch(c){case 'R':pr = new CRectangle();  // new一个对象cout << "请输入矩形的长和宽w/h:" << endl;cin >> pr->w >> pr->h;pShape[i] = pr;break;case 'C':pc = new CCircle();cout << "请输入圆形的半径r::" << endl;cin >> pc->r;pShape[i] = pc;break;case 'T':pt = new CTriangle();cout << "请输入三角形的三个边长a/b/c:" << endl;cin >> pt->a >> pt->b >> pt->c;pShape[i] = pt;break;}}qsort(pShape, n, sizeof(CShape*), MyCompare);for(i=0; i<n; i++){pShape[i] -> PrintInfo();}return 0;
}

OUT
beida_lesson % ./27
请输入几何形体的数量:
3
请输入几何形体的种类R/C/T:
R
请输入矩形的长和宽w/h:
4
5
请输入几何形体的种类R/C/T:
C
请输入圆形的半径r::
3
请输入几何形体的种类R/C/T:
T
请输入三角形的三个边长a/b/c:
3
4
5
Traingle6
Rectangle20
Rectangle28.26

3.2、铭记口诀

优点:

1、如果添加心得几何形体,比如五边形,则只需要从CShape派生出CPentagon,以及在main中的switch语句中增加一个case,其余部分不变有木有!
2、用基类指针数组存放指向各种派生类对象的指标,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法 。

铭记口诀:

1、派生类的指针可以赋值给基类指针
2、派生类的对象可以赋值给基类引用
3、用基类指针数组存放指向各种派生类对象的指标,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法 。

3.3、多态例题2:

#include <iostream>
#include <stdlib.h>
#include <math.h>using namespace std;class Base
{public:void fun1() {fun2();} // 等于void fun1() {this->fun2();}// this是基类指针,fun2是虚函数,所以是多态virtual void fun2() {cout << "Base::fun2()" << endl;}
};class Derived:public Base
{public:virtual void fun2() {cout << "Derived:fun2()" << endl;}
};int main()
{Derived d;Base * pBase = & d;// 这里pBase指针指向的是派生类的对象d,所以this指向的是派生类的fun2pBase->fun1();return 0;
}OUT:
Derived:fun2()

在非构造函数,非析构函数的成员函数中调用虚函数,是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。

  • 注意事项:派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数。

3.3、多态实例3:与构造函数

本节课程链接:看不懂对着课程就容易理解了,简单的。

  • 派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数
  • 构造函数和析构函数函数中调用虚函数不是多态
#include <iostream>
#include <stdlib.h>
#include <math.h>using namespace std;class myclass
{public:virtual void hello() {cout << "hello from myclass" << endl;};virtual void bye() {cout << "bye from myclass" << endl;}
};class son:public myclass
{public:// 派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数void hello() {cout << "hello from son" << endl;}; // 也是虚函数son() {hello();};  // 构造函数和析构函数函数中调用虚函数不是多态~son() {bye();};
};class grandson:public son
{public:void hello() {cout << "hello from grandson" << endl;}; // 虚函数void bye() {cout << "bye from grandson" << endl;};     // 虚函数grandson() {cout << "constructing grandson" << endl;};~grandson() {cout << "destructing grandson" << endl;};
};int main()
{grandson gson;  // 派生类对象,先从基类先后的构造函数开始运行。son *pson;pson = &gson;pson->hello();  // 多态return 0;
}
//OUT
hello from son
constructing grandson
hello from grandson
destructing grandson
bye from myclass

4、多态的实现原理

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

#include<iostream>
using namespace std;class A
{public:virtual void Func(){cout << "A::Func" << endl;}
};class B:public A
{public:virtual void Func(){cout << "B::Func" << endl;}
};int main()
{A a;A * pa = new B();pa -> Func();//64位程序指针位8字节long long * p1 = (long long *) & a;  // 强制类型转化为long long形long long * p2 = (long long *) pa;// 用class A虚函数表的地址替换掉派生类class B虚函数表的地址:因为虚函数表地址在前函数变量之前![请添加图片描述](https://img-blog.csdnimg.cn/direct/303255d7e6814f33b23c0d628d4990f0.png)* p2 = * p1;  pa -> Func();return 0;
}
// OUT
beida_lesson % ./30
B::Func
A::Func

5、虚析构函数、纯虚函数和抽象类

5.1、虚析构函数

请添加图片描述
对比案例如下所示:
请添加图片描述

5.2、纯虚函数和抽象类

  • 纯虚函数:没有函数体的虚函数
class A
{private: int a;public:virtual void Print() = 0;  //  纯虚函数void fun() {cout << "fun" ;}
}
  • 包含纯虚函数的类叫抽象类
    在这里插入图片描述
#include<iostream>
using namespace std;class A
{public:virtual void f() = 0; // 纯虚函数void g() {this->f()}; // OK,多态A() {// f();  //错误}
};class B:public A
{public:void f() {cout << "B:f()"<<endl;}
};int main()
{B b;b.g();return 0;
}
// OUT
B:f()

5.3、虚函数和纯虚函数的区别

  • 定义一个函数为虚函数,不代表函数为不被实现的函数。

  • 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

  • 定义一个函数为纯虚函数,才代表函数没有被实现

  • 定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

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

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

相关文章

C#实现长方体棱锥圆柱棱柱圆锥展开折叠旋转缩放

C#实现长方体棱锥圆柱棱柱圆锥展开折叠旋转缩放 C#实现 模型边数 长方体 棱锥 圆柱 棱柱 圆锥 实现功能 展开 折叠 颜色 边框颜色 旋转 缩放 大小 视图方向 项目获取&#xff1a; 项目获取&#xff1a;typora: typora/img (gitee.com) 备用项目获取链接1&#xff1a;yife…

Electron学习笔记(三)

文章目录 相关笔记笔记说明 五、界面1、获取 webContents 实例&#xff08;1&#xff09;通过窗口对象的 webContent 属性获取 webContent 实例&#xff1a;&#xff08;2&#xff09;获取当前激活窗口的 webContents 实例&#xff1a;&#xff08;3&#xff09;在渲染进程中获…

微信小程序原生组件使用

1、video组件使用 <view class"live-video"><video id"myVideo" src"{{videoSrc}}" bindplay"onPlay" bindfullscreenchange"fullScreenChange" controls object- fit"contain"> </video&g…

ubuntu server 22.04 安装docker、docker-compose

ubuntu server 22.04安装docker有两种方式&#xff0c;第一种是使用ubuntu镜像源的软件包进行安装&#xff0c;第二种使用官方GPG密钥手动添加Docker存储库方式进行安装&#xff0c;两种方式都可以&#xff0c;但第二种方式略复杂&#xff0c;这里介绍第一种比较简单的安装方式…

轻松玩转Python文件操作:移动、删除

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; Python文件操作基础 在处理计算机文件时&#xff0c;经常需要执行如移动和删除等基本操作。Python提供了一些内置的库来帮助完成这些任务&#xff0c;其中最常用的就是os模块和shutil模块。这两个模块包含了许多与文…

无需公网IP、无需云服务器,异地组网实现远程直连NAS、游戏联机

手机图片、视频太多&#xff0c;存储空间不够用怎么办?出门在外无法直连家中NAS&#xff0c;远程访问NAS速度慢&#xff1f;自建私有云、多媒体服务器&#xff0c;如何多人远程共享媒体资源&#xff1f;幻兽帕鲁、我的世界、泰拉瑞亚…局域网游戏&#xff0c;想远程多人联机&a…

Chromium 调试指南2024 Windows11篇-VSCode必要依赖扩展(四)

1. 前言 为了在Visual Studio Code中更加方便地进行Chromium项目的开发和调试&#xff0c;我们需要安装一些必要的依赖扩展。本文将介绍如何安装中文语言包及其他依赖扩展&#xff0c;以提升我们在Visual Studio Code中的开发效率和使用体验。 2. 安装依赖扩展 在打开的Visu…

局域网手机端远程控制手机

局域网手机端远程控制手机 随着科技的进步和智能设备的普及&#xff0c;远程控制技术在日常生活与工作中的应用越来越广泛。其中&#xff0c;局域网内的手机端远程控制手机技术&#xff0c;因其便捷性和实用性&#xff0c;受到了众多用户的关注。本文将简要介绍该技术及其应用…

在装有centOS7的虚拟机上进行MySQL的安装部署

1.MySQL数据库介绍 1.开源的&#xff0c;跨平台的&#xff0c;社区版免费 2.支持多种存储引擎 3.支持多种主从复制 MySQL版本&#xff1a;5.6 5.7 8.0 https://www.mysql.com MySQL官网 2.安装MySQL5.7 1.配置MySQL仓库 2.安装MySQL服务端软件 3.启动MySQL服务 s…

3. 多层感知机算法和异或门的 Python 实现

前面介绍过感知机算法和一些简单的 Python 实践&#xff0c;这些都是单层实现&#xff0c;感知机还可以通过叠加层来构建多层感知机。 2. 感知机算法和简单 Python 实现-CSDN博客 1. 多层感知机介绍 单层感知机只能表示线性空间&#xff0c;多层感知机就可以表示非线性空间。…

Ubuntu20.04 设置路由器

1. 网络拓扑图 2. 查看网卡信息 ip a得出如下网卡信息&#xff0c;enp1s0和enp2s0为两个网卡名称&#xff0c;以及相关两个网卡的详细信息&#xff0c;不同设备的网卡名称可能不一样 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group defaul…

使用python获取一下microsoft的搜索积分

主要使用的库是pyautogui PyAutoGUI接管了鼠标、键盘使用权,基本上完全照搬人的操作; 主要步骤如下: 登录edge浏览器打开搜索页面 找到搜索框的位置坐标使用pyautogui模拟点击搜索框模拟输入搜索文字模拟点击键盘enter键重复以上动作伪代码如下: import pyautogui import ti…

WM Transaction Code 仓库管理模块事务代码大全

1.1 LE-WM 仓库管理 Warehouse Management 仓库管理事务码 描述 LB01 Create Transfer Requirement 创建转储需求 LB02 Change transfer requirement 修改转储需求 LB03 Display Transfer Requirement 显示转储需求 LB10 TRs for Storage Type 按仓储类型的转储请求 …

推荐4个可用的github国内镜像

Github是全球最大的代码托管云平台&#xff0c;超过1亿用户在平台上分享代码及数据&#xff0c;深受生物信息学软件开发者的喜爱&#xff0c;并且现在发表文章&#xff0c;若涉及到代码&#xff0c;编辑还要求我们把代码及数据存放在github上&#xff0c;以便检查数据的真实性和…

frida hook java

代码例子 原函数&#xff1a; hook函数&#xff1a; if(Java.available){Java.perform(function(){var a Java.use("com.sankuai.waimai.foundation.utils.security.a");a.a.overload("java.lang.String","java.lang.String","long"…

【论文合集1】- 存内计算加速机器学习

本章节论文合集&#xff0c;存内计算已经成为继冯.诺伊曼传统架构后&#xff0c;对机器学习推理加速的有效解决方案&#xff0c;四篇论文从存内计算用于机器学习&#xff0c;模拟存内计算&#xff0c;对CNN/Transformer架构加速角度阐述存内计算。 【1】WWW: What, When, Where…

Java JVM 浅析

为什么要有JVMJVM是什么&#xff1f;JVM的工作流程和组成部分JVM规范和JVM实现JVM原理详解 带着以上问题&#xff0c;我将尝试对JVM作出一些简单的介绍。 一、JVM 简介 在90年代初&#xff0c;软件开发面临一个大问题&#xff0c;即不同的操作系统和硬件架构要求开发不同的版本…

Acrobat Pro DC 2023 for Mac:PDF处理的终极解决方案

Acrobat Pro DC 2023 for Mac为Mac用户提供了PDF处理的终极解决方案。它具备强大的文档处理能力&#xff0c;无论是查看、编辑还是创建PDF文件&#xff0c;都能轻松胜任。在编辑功能方面&#xff0c;Acrobat Pro DC 2023支持对文本、图像进行精准的修改和调整&#xff0c;还能添…

从RAID 0到RAID 10:全面解析RAID技术与应用

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、前言 1、磁盘阵列简介 2、磁盘阵列诞生背景 3、硬件RA…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-14-主频和时钟配置

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…