• 5.虚函数,覆盖,多态,异常处理


    一.虚函数,覆盖,多态
     
     1.成员函数在定义时添加了virtual关键字,这种函数叫做虚函数
     
     覆盖:如果在子类中实现了与父类中的虚函数具有相同的函数签名的函数,那么子类当中的成员函数会覆盖父类中的成员函数。
     
     多态:如果子类中的成员函数对父类中的成员函数进行了覆盖,当一个指向子类的父类指针或者引用了子类的父类引用,当调用它的虚函数,会根据的实际调用对象调用子类中的覆盖函数,而不是父类中的虚函数,这种语法现象叫做多态。
     
     多态的意义在于,同一种类发出同一种调用,而产生不同的反应。
     
     
    二.覆盖,重载,隐藏
     
     满足覆盖(重写)的条件:
      a.必须是成员函数
      b.必须是虚函数
      c.函数签名必须相同
      d.如果返回值是基本类型则必须相同(否则编译错误)
      e.如果是类类型必须是父子类关系的指针或引用(必须能进行自动类型转换)
     
     满足重载的条件:
      a.必须在同一作用域下(父子类之间不能构成重载)
      b.函数名相同但是参数列表不同
      c.const属性
      d.返回值的类型不会影响重载
     
     
     满足隐藏的条件:
      在父子类之间名字相同的标识符,只要不构成覆盖,则必定构成隐藏
     
     
    三.多态的条件
     
     1.多态特性除了父子类之间要构成覆盖,还必须是父类以指针的方式指向子类。
     
     2.当指针或引用已经构成多态时,此时调用成员函数所传的this指针
     
     3.在子类的构造函数执行前,先调用父类的构造函数,如果调用了被覆盖的虚函数,此时由于子类还没构造完成,因此只能是调用父类中的虚函数
      构造函数在进入函数体执行时,类中看的见的资源都已经全部构造完成
     
     4.在子类的析构函数完成后会调用父类的析构函数,如果调用被覆盖的虚函数,由于子类已经虚构完成,已经不能算作是完整的子类了,因此只能调用父类中的虚函数
     
     
     
    四.纯虚函数和抽象类
     1.纯虚函数
     class A
     {
     public:
      virtual void test(void) = 0;
      virtual void test(void) const = 0;
     };
     
     a.纯虚函数不需要被实现,如果非要实现也不能在类中,必须要在类外(就变成了虚函数)
     
     b.纯虚函数如果想要调用必须在子类中覆盖,然后以多态的方式调用。
     
     
     2.抽象类
     
      a.成员函数中有纯虚函数的叫做抽象类,这种类不能创建对象。
     
      b.如果子类继承了抽象类,则必须把父类中的纯虚函数覆盖,否则它也变成了抽象类,不能被实例化
     
      c.因此抽象类只能以指针或引用的方式执行子类来调用之间的虚函数
     
     
     3.纯抽象类
      所有的成员函数都是纯虚函数,这种类交纯抽象类
     
      面向对象的四大特性:抽象,封装,继承,多态
     
      纯抽象类的作用:纯抽象类是类封装的过程,同时抽象类也可以当作一个统一的接口
     
     4.纯抽象类的应用场景:
      回调模式:
       函数a由程序员A实现完成,如果程序员A想调用程序员B实现的函数b.(函数指针)
     
       函数b由程序员B实现完成,
     
      命令模式:
       输入一个命令然后执行对于的操作
     
     
      生产者与消费者模式
     
      单例模式
     
      工厂模式:一个以专们制造其他类为己任
     
      MVC模式
     
     
    五.C++中的强制类型转换
     1.C语言中的强制类型转换还能继续使用,但是不安全
     
     2.C++的强制类型转换使用很麻烦,其实是C++之父不建议使用强制类型转换,一旦代码中需要使用强制类型转换,说明代码设计的不合理,强制类型转换是一种亡羊补牢的做法
     
     static_cast<目标类型> 原类型静态类型转换
      1.子健类型的强制转换
      2.void *与其他类型指针的转换
      3.父子类之间的指针转换
      4.其他类型与void类型之间的转换
     
     const_cast 去常类型转换
     
     reinterpret_cast 重解释类型转换
      整数与指针之间的相互转换
     
     
     dynamic_cast动态类型转换
      用于构成多态的父子类之间的指针转换
     
     
     
    六.虚函数表
     
     1.什么是虚函数表?当一个类中有虚函数时,编译器会为这个类分配一个虚函数表专门记录这些虚函数,在这个类的实例中会有一个隐藏的指针成员指向这张表
     
     2.有虚函数的类,会比没有虚函数的相同的类多4个字节(具体情况还需要考虑补齐和对齐才能知道多了几个)
     
     3.一个类只有一张虚函数表,所有的对象共享一张虚函数表
     
     4.一般对象的前四个字节是指向虚函数表的指针变量
     
     
    七.动态类型绑定
     
     1.当使用父类的指针或引用指向子类时候,编译器并没有立即生成调用函数的指针,而是生成了一段代码,用于检查指针指向的真正对象是什么类型。
     
     2.在代码真正运行时才通过对象的指针找到指向虚函数表的成员指针。
     
     3.再通过成员指针访问到虚函数表,再从中找到调用的函数地址。
     
     总而言之:使用多态会产生额外的一些代码,和调用,因此使用多态会降低代码的指向速度。
     
     
     
    八.类的信息
     
     1.在C++当中,使用typeid可以获取类的一些信息,以此来确定指针指向的到底是什么类
     
     2.typeid可以直接使用来判断是否是同一种类型
      typeid(类型) == typeid(类型)
     
     3.typeid(标识符).name()获取类型名,以字符串的形式呈现
     
     4.如果typeid(指针)只能获取到指针的类型,typeid(*指针)可以获取到对象实际的类型信息
     
     
    九.虚析构
     
     1.正常情况下,如果通过父类指针指向子类对象,当使用delete释放对象时,会只调用父类的析构函数。这个时候如果在子类中申请了资源,这样就会导致内存泄漏。
     
     2.因此最好的解决办法是将父类的析构函数设置为虚析构函数
     
     3.在设计类时,如果析构函数什么都不需要做,编译器也会生成一个空的析构函数,但这样的话,会让继承它的子类有安全隐患。
     
     4.尽量把所有的析构函数都设置为虚的。
     
     
    十.异常处理
     
     1.什么是异常:能遇见但无法避免的错误
     
     2.如何抛出异常
      throw数据;
     
      a.可以抛出基本类型的异常
       throw 1;
       throw "lalalal";
     
      b.可以抛出类类型的异常
      throw Student stu;
     
      c.不要抛局部对象的指针的异常
      Student stu;
      throw &stu;
     
     3.如何捕获异常
     1  try{
     2   //可能会产生异常的代码
     3  }
     4  
     5  catch(异常类型1)
     6  {
     7   //异常处理代码1
     8  }
     9   catch(异常类型2)
    10  {
    11   //异常处理代码2
    12  }
    13   catch(异常类型3)
    14  {
    15   //异常处理代码3
    16  }
    17  
     a.在捕获异常时不光能获得异常。还能获得抛出的异常数据
     
     b.如果异常抛出了但是没有被捕获,程序会结束
     
     c.异常的捕获是自上而下的,不是选择是优,因此子类的异常捕获最好放在父类的前面
     
     d.捕获异常时尽量使用引用的方式,由于抛出异常如果使用对象的方式来捕获会调用对象的拷贝构造,这样会在拷贝对象过程中再次引发异常
     
     4.类类型的异常
      a.可以为每一种异常定义一个什么都不用做的类,它知识为了区分各种异常
     
      b.在抛出异常的时候可能会调用异常的构造,拷贝构造,赋值构造等,如果在类中有看不到的资源,一定要把这三个函数重定义
     
      c.为了防止有自定义的异常无法被捕获,因此我们在定义异常类时,最好都继承标准库的异常类,这么哪怕,不能精准的捕获异常,也能
     
      d.在抛异常的时候尽量抛匿名临时对象
     
     5.编译器会生成一段用来申请"安全区"的代码并保护它,在异常发生后,此时程序的节奏已经被打乱没有哪个地方是安全的除了安全区。安全区能保证存储在此位置的异常对象不受破坏。
     
     6.构造和析构函数中的异常
      在构造函数中发生了异常后,会直接跳转到异常处理代码,异常的构造就此中断,对象的构造就不完整了,不完整的对象永远不可能调用析构函数,哪怕你用delete显示调用。
     
      在构造函数的异常可以抛,但是不要抛出构造函数(内部处理),一般使用回滚机制
     
     
    十一.文件IO
     操作文件的类:ifstream,ofstream,fstream
     open打开文件
      打开文件的方式:
       in 读 ifstream/fstream
       out 写ofstream/fstream
       app 以追加的方式打开,如果文件不存在则不创建,存在不清空,有写权限
       ate 打开文件时定位到文件末尾
       binary 以二进制方式打开文件
       trunc 以清空方式打开文件
       in | out
     文本文件的读写使用<<,>>,与cin,cout类似
     read二进制方式读文件
     write二进制方式写文件
     seek调整文件的位置指针
     
  • 相关阅读:
    通过Thread Pool Executor类解析线程池执行任务的核心流程
    Notebook交互式完成目标检测任务
    3 万测试人疯抢的《高薪测试成长图谱》,1500 本限量免费赠送,先到先得!
    招募令 | 霍格沃兹测试学院 MVP 开放申请通道
    这样准备面试,成功率才高
    测试圈伪“套路” 细数程序员的那些“凡尔赛语录”
    技术分享 | 黑盒测试方法论—边界值
    只需搞定Docker,环境问题再也不是测开路上的『坑』
    Python 自动化测试(五): Pytest 结合 Allure 生成测试报告
    自动化测试实战 | 搞定 PageObject 设计模式(附源码)
  • 原文地址:https://www.cnblogs.com/LyndonMario/p/9524559.html
Copyright © 2020-2023  润新知