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

news2024/11/20 4:17:23

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::Print
    pa = pb;
    pa -> Print();   // b.Print()被调用,输出:B::Print
    pa = pd;
    pa -> Print();   // d.Print()被调用,输出:D::Print
    pa = pe;
    pa -> Print();   // e.Print()被调用,输出:E::Print

    return 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数组中的元素,数组元素的类型是CShape
    p2 = (CShape * *)s2;   // 故p1,p2都是指向指针的指针,类型为CShape **
    a1 = (*p1)->Area();    // * p1的类型是Cshape *,是基类指针,故此句为多态
    a2 = (*p2)->Area();
    if(a1<a2)
        return -1;
    else if(a2 < a1)
        return 1;
    else
        return 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指向的是派生类的fun2
    pBase->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.coloradmin.cn/o/1675265.html

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

相关文章

汇昌联信:拼多多网店该如何开店?

拼多多网店的开设流程并不复杂&#xff0c;但需要细心和耐心去完成每一步。下面将详细阐述如何开设一家拼多多网店。 一、选择商品与定位 开设拼多多网店的第一步是确定你要销售的商品类型&#xff0c;这决定了你的目标客户群体和市场定位。你需要了解这些商品的市场需求、竞争…

MacApp自动化测试之Automator初体验

今天我们继续讲Automator的使用。 初体验 启动Automator程序&#xff0c;选择【工作流程】类型。从资源库区域依次将获取指定的URL、从网页中获得文本、新建文本文件三个操作拖进工作流创建区域。 然后修改内容&#xff0c;将获取指定的URL操作中的URL替换成https://www.cnb…

MATLAB科技绘图与数据分析

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

Token 计费与计算、tiktoken介绍

Token怎么计算的&#xff1f; 每个模型都具有不同的功能和价格。价格是按照每1000个 Token 计算的。您可以将Token视为单词的组成部分&#xff0c;其中1000个Token 大约相当于750个单词。(这段文字包含35个Token) 可以看到35个颜色块 每个块都有ID 英文、中文 都会有单独的一个…

【C++】学习笔记——继承_2

文章目录 十二、继承5. 继承与友元6. 继承与静态成员7. 复杂的菱形继承及菱形虚拟继承 未完待续 十二、继承 5. 继承与友元 友元关系不能继承&#xff0c;也就是说父类友元不能访问子类私有和保护成员 。除非子类也设置成友元。 6. 继承与静态成员 父类定义了 static 静态成…

【渲染数学-01】如何模拟静态流(上)

文章目录 一、说明二、摘要三、简介四、稳定的纳维-斯托克斯4.1 基本方程4.2 解决方法 一、说明 关于流体物质的仿真和模拟&#xff0c;需要流体理论方面的一般知识。我们这里从基本流体方程入手&#xff0c;详细解释如何实现流体仿真的每一个具体步骤。 二、摘要 构建类似流体…

在React中利用Postman测试代码获取数据

文章目录 概要名词解释1、Postman2、axios 使用Postman测试API在React中获取并展示数据小结 概要 在Web开发中&#xff0c;通过API获取数据是一项常见任务。Postman是一个功能强大的工具&#xff0c;可以帮助开发者测试API&#xff0c;并查看API的响应数据。在本篇博客中&…

vue3中的watch侦听器

在有些情况下&#xff0c;我们需要在状态变化时执行一些“副作用”&#xff1a;例如更改 DOM &#xff0c;或是根据异步操作的结果去修改另一处的状态。在组合式 API 中&#xff0c;我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数。 watch 函数可以侦听被 ref…

PXE+Kickstart无人值守安装安装Centos7.9

文章目录 一、什么是PXE1、简介2、工作模式3、工作流程 二、什么是Kickstart1、简介2、触发方式 三、无人值守安装系统工作流程四、实验部署1、环境准备2、服务端&#xff1a;关闭防火墙和selinux3、添加一张仅主机的网卡4、配置仅主机的网卡4.1、修改网络连接名4.2、配IP地址4…

太阳能光伏发电应用过程中会用到哪些光伏组件?

随着全球对可再生能源的需求日益增加&#xff0c;太阳能光伏发电已成为一种重要的清洁能源解决方案。在太阳能光伏发电系统的运行过程中&#xff0c;光伏组件作为系统的核心部分&#xff0c;起着至关重要的作用。本文将详细介绍太阳能光伏发电应用过程中会使用到的关键光伏组件…

互联网搞钱大变天,这有几条活路

互联网搞钱大变天&#xff0c;这有几条活路 靠互联网营生的各位同胞&#xff0c;你们有没有想过这样一个问题&#xff1a;假如有一天你的自媒体账号全被封了&#xff0c;你手上的操作项目全都黄了&#xff0c;你会怎么办&#xff1f; 就封号这事在这几年相信大家都不会陌生&a…

gin框架学习笔记(三) ——路由请求与相关参数

参数种类与参数处理 查询参数 在讲解查询参数的定义之前&#xff0c;我们先来看一个例子&#xff0c;当我打开了CSDN&#xff0c;我现在想查看我的博客浏览量&#xff0c;那么我就需要点击我的头像来打开我的个人主页,像下面这样: 我们现在把浏览器的网址取下来&#xff0c;…

在linux里登录远程服务器

在linux里登录远程服务器。在虚拟终端里输入命令&#xff1a; ssh 远程服务器ip -l username 然后输入登录密码&#xff0c;就可以登录到远程服务器的命令行界面。登录方便&#xff0c;字体也可以在本地机的虚拟终端里设置得大一点。 下面是一张截屏图片。

【高阶数据结构】LRU Cache -- 详解

一、什么是 LRU Cache LRU&#xff08;Least Recently Used&#xff09;&#xff0c;意思是最近最少使用&#xff0c;它是一种 Cache 替换算法。 什么是 Cache&#xff1f; 狭义的 Cache 指的是位于 CPU 和主存间的快速 RAM&#xff0c;通常它不像系统主存那样使用 DRAM 技术&…

二叉树基础oj练习【11道题】

二叉树基础oj练习 1.单值二叉树 题目&#xff1a; 单值二叉树 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;[1,1,1…

Pycharm导入自定义模块报红

文章目录 Pycharm导入自定义模块报红1.问题描述2.解决办法 Pycharm导入自定义模块报红 1.问题描述 Pycharm 导入自定义模块报红&#xff0c;出现红色下划线。 2.解决办法 打开【File】->【Setting】->【Build,Execution,Deployment】->【Console】->【Python Con…

HTML的使用(中)

文章目录 前言一、HTML表单是什么&#xff1f;二、HTML表单的使用 &#xff08;1&#xff09;<form>...</form>表单标记&#xff08;2&#xff09;<input>表单输入标记总结 前言 在许多网页平台上浏览&#xff0c;大多逃不了登录账号。此时在网页中填写的用户…

5G消息和5G阅信的释义与区别 | 赛邮科普

5G消息和5G阅信的释义与区别 | 赛邮科普 在 5G 技术全面普及的当下&#xff0c;历史悠久的短信服务也迎来了前所未有的变革。5G 阅信和 5G 消息就是应运而生的两种短信形态&#xff0c;为企业和消费者带来更加丰富的功能和更加优质的体验。 这两个产品名字和形态都比较接近&am…

在数据库中使用存储过程插入单组/多组数据

存储过程可以插入单组数据&#xff0c;也可以以字符串的形式插入多组数据&#xff0c;将字符串中的信息拆分成插入的数据。 首先建立一个简单的数据库 create database student; use student;选中数据库之后建立一张学生表 create table stu(uid int primary key,uname varc…

解决Android Studio Gradle下载慢的问题

安卓 gradle-7.5-bin.zip 下载慢 https://mirrors.cloud.tencent.com/gradle/7.x.x 找到对应匹配版本 把下载的文件直接复制到 C:\Users\Administrator.gradle\wrapper\dists\gradle-x.x\ 中对应版本目录下&#xff0c;例如需要下载 gradle-2.14.1-all.zip&#xff0c;则下载好…