写前声明:参考链接 C++面经、面试宝典 等
✊✊✊每日一面——虚拟继承、抽象类和四种类型转化
- 一、什么是虚拟继承?
- 二、如何理解抽象类?
- 三、抽象基类为什么不能创建对象?
- 四、成员函数里memset(this,0,sizeof(*this))会发生什么
- 五、强制类型转换有哪几种,分别有哪些特点?原理是什么?
一、什么是虚拟继承?
- C++支持多继承,除了public、private、protected三中继承方式外,还提供虚拟继承 virtual ,如果类B和类C同时公有继承类A,而类D有多重继承类B、C,这这种方式被叫做菱形继承
- 虚拟继承的情况下,无论基类被继承多少次,只会存在一个实体
- 虚拟继承基类的子类中,子类会增加某种形式的指针,或者指向虚基类对象,或者指向一个相关的表格;表格中存放的不是虚基类子对象的地址,就是其偏移量,此类这种被称为虚基类指针 bptr,而表格则是虚基类表,如果既存在虚函数指针,又存在虚基类指针,某些编译器会将其优化为一个指针
- 在虚拟继承下,无论一个公有的基类子对象在派生类层次中出现了多少次,只会有一个共享的基类子对象被继承,共享的基类子对象就是虚基类,在此机制下,基类子对象的多份复制产生的二义性问题就被消除了
二、如何理解抽象类?
-
抽象类的定义如下
- 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 “=0”,就叫做抽象类
抽象类有如下几个特点:
- 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 “=0”,就叫做抽象类
-
抽象类有如下几个特点:
- 1)抽象类只能用作其他类的基类,不能建立抽象类对象。
- 2)抽象类不能用作参数类型、函数返回类型或显式转换的类型。
- 3)可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性
三、抽象基类为什么不能创建对象?
抽象类是一种特殊的类,它是为了抽象和设计目的来建立的,处于继承层次结构的较上层
-
抽象类的定义: 带有纯虚函数的类
-
抽象类的作用: 将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,将其传给子类,子类可以具体实现这些语义,也可以将其传给自己的子类
-
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。 如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。抽象类是不能定义对象的。一个纯虚函数不需要(但是可以)被定义
-
纯虚函数定义: 纯虚函数是一种特殊的虚函数,它的一般格式为:
class <类名> { virtual <类型><函数名>(<参数表>)=0; … };
- 在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。 纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。
-
纯虚函数引入原因:
- 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
- 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔 雀等子类,但动物本身生成对象明显不合常理。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)。若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重载以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。例如,绘画程序中,shape作为一个基类可以派生出圆形、矩形、正方形、梯形等, 如果我要求面积总和的话,那么会可以使用一个 shape * 的数组,只要依次调用派生类的area()函数了。如果不用接口就没法定义成数组,因为既可以是circle ,也可以是square ,而且以后还可能加上rectangle,等等.
四、成员函数里memset(this,0,sizeof(*this))会发生什么
- 类函数有虚函数表:这么做会破坏虚函数表,后续对虚函数的调用都将出现异常
- 类中含有C++类型的对象:如,类中定义了一个list的对象,由于在构造函数体的代码执行之前就对list对象完成了初始化,假设list在它的构造函数里分配了内存,那么我们这么一做就破坏了list对象的内存。
五、强制类型转换有哪几种,分别有哪些特点?原理是什么?
Static_cast: 用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型,主要突出一个 联系!!!
- 主要用法:
- 用于类层次结构中基类和派生类之间指针或者引用的转换,进行上行切换(把派生类的指针或者引用转换成基类)是安全的,若是下行转换,由于没有动态类型检查,不安全
- 用于基本数据类型的转换,如 int -> char
- 把空指针转换成目标类型的空指针
- 把任意类型的表达式转换成void类型
-
Const_cast: 用于强制去除类似于const这种不能被修改的常数特性
- 用法:
- 用来修改类型的const或者volatile属性,除了const或volatile修饰之外,type_id和expression的类型是一样的。
- 常量指针被转化为非常量指针,并且仍然指向原来的对象
- 常量引用被转换为非常量引用,并且仍指向原来的对象,常量对象被转换成非常量对象。
注意:const_cast不适用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,即去除常量性的对象必须为指针或者引用。
Reinterpret_cast: 用于改变指针或引用的类型,将指针或者引用转换成一个足够长的整形,将整形转换为指针或引用
- 用法:
- 传入一个类型必须是一个指针,引用,算术类型,函数指针,成员函数,成员指针
- 它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针
Dynamic_cast: 其他三种都是在编译时完成的,它是在运行时处理的,运行时要进行类型检查。
- 用法:
- 不能用于内置的基本数据类型的强制转换。
- 如果转换成功会返回一个指向类的指针或者引用,转换失败会返回NULL。
- 进行转换的时候基类中一定要有虚函数,否则编译不通过(因为类中存在虚函数就说明它有想让基类指针或引用指向派生类对象的情况,此时转换才有意义)。
- 在类的转换时,在类层次间进行上行转换时,与static_cast的转换效果是一样的,在下行转换时,它具有类型检查功能,比static_cast更安全。
注意:向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。如果转换目标是指针类型转换失败,则结果返回0,如果是引用类型则抛出std::bad_cast异常