cpp_11_虚函数_多态_纯虚函数

news2025/1/27 12:53:22

                编译器根据指针的类型来确定调用哪个类的普通成员函数 

                编译器根据基类类型指针指向的对象类型,来确定调用哪个类的虚函数 

0  非虚的世界(全普通函数)

        对象的自洽性:

        1)对同样的函数调用,各个类的对象都会做出恰当的响应。

        

        编译器仅根据指针的类型来确定调用哪个类的普通成员函数

        即,通过基类类型指针调用普通成员函数,只能调用基类的成员函数:

        1)即便这个基类类型的指针指向了子类对象,调用的也为基类的成员函数。

        2)一旦调用子类所特有的成员函数,将引发编译错误

// selfconst.cpp 非虚的世界(没有虚函数的程序)
#include <iostream>
using namespace std;

class Shape {
public:
    void Draw( ) {  cout << "Shape::Draw" << endl;  }
private:
    int m_x;
    int m_y;
};

class Rect : public Shape {
public:
    void Draw( ) {  cout << "Rect::Draw" << endl;   }
private:
    int m_rx;
    int m_ry;
};

class Circle : public Shape {
public:
    void Draw( ) {  cout << "Circle::Draw" << endl; }
    void foo( ) { }
private:
    int m_radius;
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    cout << "--------利用 对象 调用 非虚的成员函数---------" << endl;
    // 哪个类对象 就调用 哪个类的普通成员函数(对象的自恰性)
    Shape s;
    s.Draw( ); // Shape::Draw
    Rect r;
    r.Draw( ); // Rect::Draw
    Circle c;
    c.Draw( ); // Circle::Draw
    cout << "------利用 指针 调用 非虚的成员函数----------" << endl;
    // 利用 基类类型的指针 只能调用 基类的普通成员函数
    Shape* ps = &s;
    ps->Draw( ); // Shape::Draw
    // 即便 基类类型指针 指向的是子类对象,调用仍然为基类的普通成员函数
    ps = &r;
    ps->Draw( ); // Shape::Draw
    ps = &c;
    ps->Draw( ); // Shape::Draw
    // 如果调用 子类所特有成员函数,将报告编译器错误
//  ps->foo( );

    // 编译器 简单而粗暴根据 指针本身的类型 来确定到底调用哪个类的普通成员函数
    return 0;
}

1  虚函数(虚方法)

        class  类名 {

                virtual  返回类型  函数名 ( 形参表 ) { ... }

        };

        覆盖:

        1)如果子类的成员函数和基类的虚函数具有相同的函数签名,则该成员函数就也是虚函数,无论其是否带有virtual关键字。

        2)与基类的虚函数构成覆盖关系。

        通过基类类型指针调用虚函数:

        1)如果基类型指针指向基类对象,则调用基类的原始版本虚函数。

        2)如果基类型指针指向子类对象,则调用子类的覆盖版本虚函数。

// bdv.cpp 虚的世界(有虚函数的程序)
#include <iostream>
using namespace std;

class Shape {
public:
    virtual void Draw( ) = 0; // 虚函数(原始版本)
private:
    int m_x;
    int m_y;
};

class Rect : public Shape {
public:
    void Draw( ) {  cout << "Rect::Draw" << endl;   } // 虚函数(编译器补virtual),
                                              //与基类Draw函数构成覆盖关系(覆盖版本)
private:
    int m_rx;
    int m_ry;
};

class Circle : public Shape {
public:
    virtual void Draw( ) {  cout << "Circle::Draw" << endl; } // 虚函数(编译器
                        //不补virtual),与基类的Draw函数构成覆盖关系(覆盖版本)
private:
    int m_radius;
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    cout << "--------利用 对象 调用 虚的成员函数---------" << endl;
    // 哪个类对象 就调用 哪个类的虚成员函数(对象的自恰性)
//    Shape s;
//    s.Draw( ); // Shape::Draw
    Rect r;
    r.Draw( ); // Rect::Draw
    Circle c;
    c.Draw( ); // Circle::Draw

    
    cout << "------利用 指针 调用 虚的成员函数----------" << endl;
    
//    Shape* ps = &s;
//    ps->Draw( ); // Shape::Draw (不是多态)
    
    Shape* ps = &r;
    ps->Draw( ); // Rect::Draw(多态)
    
    ps = &c;
    ps->Draw( ); // Circle::Draw(多态)
    
    // 根据 指针指向的对象的类型 来确定到底调用哪个类的虚成员函数
    return 0;
}

2  动态多态(虚函数的主要应用)

2.1  静态多态和动态多态

        静态多态:在编译阶段就已经绑定了函数地址,主要体现是重载、模板。

        动态多态:利用虚函数实现,在运行期间绑定,主要体现是给父类指针传递不同的类型,调用的函数也会不同。

        如果子类提供了对基类虚函数的有效覆盖,那么通过一个基类型指针指向子类对象),或者基类型引用引用子类对象),调用该虚函数,实际调用的将是子类中的覆盖版本,而非基类中的原始版本,这种现象称为动态多态。

        动态多态的重要意义在于,一般情况下,调用哪个类的成员函数是由指针或引用本身的类型决定的,而当多态发生时,调用哪个类的成员函数是由指针或引用的实际目标对象的类型决定的。

2.2  动态多态的必要条件

        1)基类中定义虚函数,子类提供覆盖版本

        2)基类型指针(指向子类对象)或基类型引用(引用子类对象),调用该虚函数

2.3  动态多态和this指针

        调用虚函数的指针也可以是基类中的this指针,同样能满足多态的2个必要条件,

        但在构造析构函数中除外。

// this.cpp 多态 和 this指针
#include <iostream>
using namespace std;

class Base {
public:
    void foo( /* Base* this */ ) {
        cout << "foo函数中调用的为: ";
        this->vfun();
    }
    Base( /* Base* this */ ) {
        cout << "构造函数中调用的为: ";
        this->vfun();
    }
    ~Base( /* Base* this */ ) {
        cout << "析构函数中调用的为: ";
        this->vfun();
    }
    virtual void vfun() { cout << "Base::vfun()" << endl; } // 原始版本
};

class Derived : public Base {
public:
    Derived( ) {
        //【Base();】定义 基类子对象,利用 基类子对象.Base()
    }
    ~Derived() {
        // 对于 基类子对象,利用 基类子对象.~Base()
    }
    void vfun() { cout << "Derived::vfun()" << endl; } // 覆盖版本
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Derived d; // 定义d,利用d.Derived()
    d.foo(); // foo( &d )
    return 0;
} // d.~Derived() 

2.4  动态多态揭秘(虚函数表)

        

        动态绑定(非编译,而是程序执行时的操作): 

        当编译器看到通过指针或引用,调用虚函数的语句时,并不急于生成有关函数跳转的指令,相反,编译器会用一段代码替代该语句,这段代码在运行时才能被执行,完成如下操作:

        1)确定指针或引用的目标对象所占内存空间  

        2)从目标对象所占内存中间中找到虚表指针  

        3)利用虚表指针找到虚函数表  

        4)从虚函数表中获取所调用虚函数的入口地址  

        5)根据入口地址,调用该虚函数  

// vftable.cpp 多态揭秘 -- 虚函数表
#include <iostream>
using namespace std;

class A { // 编译器根据A类的信息,将制作一张虚函数表 A::foo的地址 A::bar的地址
public:
    virtual void foo() { cout << "A::foo" << endl; }
    virtual void bar() { cout << "A::bar" << endl; }
};

class B : public A { //编译器根据B类的信息,将制作一张虚函数表 B::foo的地址 A::bar的地址
public:
    void foo() { cout << "B::foo" << endl; }
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    A a; // |虚表指针|-->编译器根据A类的信息制作的虚函数表
    cout << "a对象的大小:" << sizeof(a) << endl; // 8

    B b; // |虚表指针|-->编译器根据B类的信息制作的虚函数表
    cout << "b对象的大小:" << sizeof(b) << endl; // 8

    void(**pfunc)() = *((void(***)())&a); // 从a中获取虚表指针
    pfunc[0](); // A::foo
    pfunc[1](); // A::bar 

    void(**pfunc2)() = *((void(***)())&b); // 从b中获取虚表指针
    pfunc2[0](); // B::foo
    pfunc2[1](); // A::bar

    A* pa = &b;
    pa->foo(); // 编译器在编译期间 并不知道 调用 哪个类的foo函数

    // 1. 根据pa获取b对象所占内存空间
    // 2. 从b对象所占内存空间中 获取 虚表指针
    // 3. 根据 虚表指针 找到 编译器根据B类的信息制作的虚函数表
    // 4. 从 虚函数表中 获取 虚函数的入口地址
    // 5. 利用 函数指针 调用 虚函数
    

    // 调用普通成员函数执行效率高 

    return 0;
}

        动态绑定对性能的拖累:

        1) 虚函数表本身会增加进程内存空间的开销 

        2)与普通函数调用相比虚函数调用要多出几个步骤,增加运行时间的开销

        3)无法内联:动态绑定会妨碍编译器通过内联来优化代码

        故,只有在确实需要多态特性的场合才建议使用虚函数,负责尽量使用普通函数

3  纯虚函数(抽象方法)

        class  类名{

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

         }

4  抽象类

        拥有纯虚函数的类称为抽象类

        抽象类不能实例化为对象

        抽象类的子类如果不对基类中的全部纯虚函数有效覆盖,那么该子类也是抽象类

5  纯抽象类(接口)

        全部由纯虚函数构成的抽象类称为纯抽象类或接口。

// abstract.cpp 纯虚函数 和 抽象类
#include <iostream>
using namespace std;

class A { // 抽象类
public:
    void bar() {}
    virtual void foo() = 0; // 纯虚函数
};

class B : public A {
public:
    void foo() {
        // ...
    }
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
//  A a;
//  new A;

    B b;
    new B;
    return 0;
}

6  多态练习

        练习:设计一个通用的线程类。

// tread.cpp 多态练习:设计一个通用的线程类
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <unistd.h>
using namespace std;
// 设计一个“通用”的线程类
class Thread {
public:
    void start( /* Thread* this */ ) {
        pthread_create(&m_tid, NULL, threadfunc, this );
    }
    static void* threadfunc( void* arg ) {
        // 子线程需要的执行操作,就不应该有类的设计者提供
        // 子线程需要的执行操作,就应该由用户来提供
        // 我们线程处理函数 调用 用户提供的操作
        Thread* p = (Thread*)arg;
        p->run( );

    }
    virtual void run( ) { }
private:
    pthread_t m_tid;
};
// 以上代码模拟类的设计者
// -------------------------
// 以下代码模拟类的使用者
class MyThread : public Thread {
public:
    MyThread( int sec, char ch ) : m_sec(sec), m_ch(ch) {}
    void run( ) {
        for( ;; ) {
            usleep( 1000*m_sec );
            cout << m_ch << flush;
        }  
    }
private:
    int m_sec;
    char m_ch;
};
int main( void ) {
    MyThread t1(500,'+'), t2(1000,'*');
    t1.start( );
    t2.start( );
    getchar( );
    return 0;
}

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

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

相关文章

WEB前端人机交互导论实验-实训2格式化文本、段落与列表

1.项目1 文本与段落标记的应用&#xff1a; A.题目要求: B.思路&#xff1a; &#xff08;1&#xff09;首先&#xff0c;HTML文档的基本结构是通过<html>...</html>标签包围的&#xff0c;包含了头部信息和页面主体内容。 &#xff08;2&#xff09;在头部信息…

Kubernetes (十二) 存储——Volumes配置管理

一. 卷的概念 官方地址&#xff1a;卷 | Kuberneteshttps://v1-24.docs.kubernetes.io/zh-cn/docs/concepts/storage/volumes/ 二. 卷的类型及使用 …

vue3+threejs可视化项目——搭建vue3+ts+antd路由布局(第一步)

文章目录 ⭐前言&#x1f496;vue3系列相关文章 ⭐搭建vue3项目过程&#x1f496; 初始化项目&#x1f496; 添加antd和router依赖&#x1f496; vite配置项映射目录和代理&#x1f496; antd国际化&#x1f496; layout布局封装&#x1f496; vite读取modules目录文件作为路由…

【beyond compare】默认不比较文件结尾

默认不比较文件结尾 git服务端的代码是UNIX 编码的&#xff0c;但是本地visual studio 是PC的&#xff0c; 代码一样&#xff0c;但是编码不同&#xff0c;导致compare 无法区分。 这位大神解决了这个问题,亲测可用&#xff1a; Beyond Compare之PC与UNIX文件比较问题 感谢大…

【Java】JDBC 数据库连接 (JDK17+MySQL8)

文章目录 JDBC 是什么&#xff1f;导入JDBC jar包一、JDBC的核心API和使用路线二、基于 statement 演示 查询三、基于 statement 查询的改进与问题四、基于 preparedStatement 方式优化五、基于 preparedStatement 演示 CRUDC 、增加数据R、查询数据U、修改/更新 数据D、删除数…

Vant-ui图片懒加载

核心代码 在你的全局顶部引入和初始化 Vue.use(vant.Lazyload, {loading: /StaticFile/img/jiazai.jpg,error: /StaticFile/img/jiazai.jpg,lazyComponent: false, });//图片懒加载 <img v-lazy"https://img-blog.csdnimg.cn/direct/3d2c8a7e2c0040488a8128c3e381d58…

《教育界》期刊怎么投稿发表论文?

《教育界》是国家新闻出版总署批准的正规教育类期刊&#xff0c;由广西师范大学主管&#xff0c;广西师范大学出版社集团有限公司主办&#xff0c;面向国内外公开发行&#xff0c;旨在追踪教育新动向&#xff0c;探讨教育改革与管理、办学与教育教学经验等&#xff0c;为广大一…

【服务器数据恢复】Hyper-V虚拟化数据恢复案例

服务器数据恢复环境&#xff1a; Windows Server操作系统服务器&#xff0c;部署Hyper-V虚拟化环境&#xff0c;虚拟机的硬盘文件和配置文件存放在某品牌MD3200存储中&#xff0c;MD3200存储中有一组由4块硬盘组成的raid5阵列&#xff0c;存放虚拟机的数据文件&#xff1b;另外…

Elasticsearch的基本功能和使用

Elasticsearch &#xff0c;简称为 ES&#xff0c;是一款非常强大的开源的高扩展的分布式全文 检索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容,它可以近乎实时的 存储、检索数据.还可以可以实现日志统计、分析、系统监控等功能. 官网:https://www.elastic.c…

前端性能优化之数据存取,存储以及缓存技术

无论是哪种计算机语言&#xff0c;说到底它们都是对数据的存取与处理。若能在处理数据前&#xff0c;更快地读取数据&#xff0c;那么必然会对程序执行性能产生积极的作用。 一般而言&#xff0c;js的数据存取有4种方式。 直接字面量:字面量不存储在特定位置也不需要索引&…

Java控制结构解析

在 Java 编程语言中&#xff0c;控制结构用于控制程序的执行流程。以下是几种常见的控制结构及其解析&#xff1a; 条件语句&#xff08;If-else 语句&#xff09;&#xff1a;根据条件的真假来执行不同的操作。Switch 语句&#xff1a;根据一个表达式的值&#xff0c;选择不同…

QT上位机开发(权限管理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果只是简单的工具软件&#xff0c;其实是没有权限管理这么一说的。比如说&#xff0c;串口工具、485工具之类的软件&#xff0c;其实根本不存在所…

NLP论文阅读记录 - WOS | 2023 TxLASM:一种新颖的与语言无关的文本文档摘要模型

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.文献综述及相关工作三.本文方法四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 TxLASM: A novel language agnostic summarization mo…

iOS上h5长按识别图片二维码,图片会默认放大,禁用这一默认行为

iOS上h5长按识别图片二维码&#xff0c;图片会默认放大&#xff0c;禁用这一默认行为 测试代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-widt…

RocketMQ源码阅读-Producer消息发送

RocketMQ源码阅读-Producer消息发送 1. 从单元测试入手2. 启动过程3. 同步消息发送过程4. 异步消息发送过程5. 小结 Producer是消息的生产者。 Producer和Consummer对Rocket来说都是Client&#xff0c;Server是Broker。 客户端在源码中是一个单独的Model&#xff0c;目录为rock…

精确掌控并发:固定时间窗口算法在分布式环境下并发流量控制的设计与实现

这是《百图解码支付系统设计与实现》专栏系列文章中的第&#xff08;14&#xff09;篇。点击上方关注&#xff0c;深入了解支付系统的方方面面。 本篇主要介绍分布式场景下常用的并发流量控制方案&#xff0c;包括固定时间窗口、滑动时间窗口、漏桶、令牌桶、分布式消息中间件…

python + ddt数据驱动 之 多个参数

案例&#xff1a;打开https://www.csdn.net/&#xff0c;进行登录&#xff0c;查看结果 不使用ddt数据驱动&#xff1a; import unittest from selenium import webdriver import timeclass CSDNTestCase(unittest.TestCase):def setUp(self):# 打开chrome浏览器self.driver …

vue2实现日历12个月平铺,显示工作日休息日

参考&#xff1a;https://blog.csdn.net/weixin_40292154/article/details/125312368 1.组件DateCalendar.vue&#xff0c;sass改为less <template><div class"cc-calendar"><div class"calendar-title"><span>{{ year }}年{{ mo…

线性调频信号的解线调(dechirp,去斜)处理matlab仿真

线性调频信号的解线调 线性调频信号的回波模型参考信号去斜处理去斜处理傅里叶变换得到脉压结果解线调仿真总结 线性调频信号的回波模型 对于线性调频脉冲压缩雷达&#xff0c;其发射信号为&#xff1a; s ( t ) r e c t ( t T ) e x p ( j π μ t 2 ) \begin{equation} s(…

C++深入学习之STL:1、容器部分

标准模板库STL的组成 主要由六大基本组件组成&#xff1a;容器、迭代器、算法、适配器、函数对象(仿函数)以及空间配置器。 容器&#xff1a;就是用来存数据的&#xff0c;也称为数据结构。 本文要详述的是容器主要如下&#xff1a; 序列式容器&#xff1a;vector、list 关联…