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调整文件的位置指针
 
posted @ 2018-08-23 16:25  LyndonMario  阅读(236)  评论(0编辑  收藏  举报