c++ 面试:多态,继承题总结

什么是多态

一个接口,多种实现

虚表指针是在什么时候,或者什么地方初始化呢?

答案:构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,1.要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,2.当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。
由于pFather实际指向的对象类型是Son,因此vptr指向的Son类的vtable,当调用pFather->Son()时,根据虚表中的函数地址找到的就是Son类的Say()函数.
就是通过指针或引用才是动态绑定,通过点运算是不可以的。

多态分类(重载,重写(覆盖,虚函数))

静态多态:重载。静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错。
c++ 面试:多态,继承题总结_第1张图片
动态多态:虚函数是。在运行期间。在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

函数重载

必须在同一个类中进行
子类无法重载父类的函数,父类同名函数将被名称覆盖
重载是在编译期间根据参数类型和个数决定函数调用

函数重写

必须发生于父类与子类之间
并且父类与子类中的函数必须有完全相同的原型
使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
多态是在运行期间根据具体对象的类型决定函数调用

动态多态的条件

  1. 基类中必须包含虚函数,并且派生类中一定要对基类中的虚函数进行重写。
  2. 通过基类对象的指针或者引用调用虚函数。

不能定义为虚函数

1)友元函数,它不是类的成员函数 2)全局函数 3)静态成员函数,它没有this指针
4)构造函数,拷贝构造函数,以及赋值运算符重载(可以但是一般不建议作为虚函数)

纯虚函数(抽象类)

在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。纯虚函数是一定要被继承的,否则它存在没有任何意义
c++ 面试:多态,继承题总结_第2张图片

派生类虚表

1.先将基类的虚表中的内容拷贝一份

2.如果派生类对基类中的虚函数进行重写,使用派生类的虚函数替换相同偏移量位置的基类虚函数

3.如果派生类中新增加自己的虚函数,按照其在派生类中的声明次序,放在上述虚函数之后

多态缺陷

●降低了程序运行效率(多态需要去找虚表的地址)

●空间浪费

多态实现的三个条件

有继承、有virtual重写、有父类指针(引用)指向子类对象。

多态的C++实现

virtual关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用

为什么要定义虚析构函数(在什么情况下应当声明虚函数)

构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数

析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象

inline函数可以实虚函数码

不可以,因为inline函数没有地址,无法将他存放到虚函数表中。

构造函数可以是虚函数吗

不可以,因为对象中的虚函数指针是在对象构造的时候初始化的。

析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

可以,最好将析构函数设置为虚函数,因为这样可以避免内存泄漏的问题,如果一个父类的指针指向了子类的的对象,如果子类对象中的虚函数没有写成多态的,他只会调用父类的析构函数,不会调用自己的析构函数,但是他创建对象的时候调用了构造函数,所以说就用子类的构造函数就应该该取调用他的析构函数,这样才能保证所有的必须释放的资源都是放了,才可以保证不会有内存泄漏。如果是多态的,就会先去调用子类的析构函数,然后再取调用父类的析构函数,这样子类和父类的资源就都可以释放。

对象访问普通函数快还是虚函数快?

如果是普通对象,是一样快的,如果是指针对象或者是引用对象,调用普通函数更快一些,因为构成了多态,运行时调用虚函数要先到虚函数表中去查找。这样然后才拿到韩式的地址,这样就不如直接可以拿到函数地址的普通函数快。

虚函数表时再什么阶段生成的?他存放在哪里?

虚函数时在编译阶段生成的,他一般存放再代码段,也就是常量区。

虚函数表指针被编译器初始化的过程怎么理解的?

当类中声明了虚函数是,编译器会在类中生成一个虚函数表VS中存放在代码段,虚函数表实际上就是一个存放虚函数指针的指针数组,是由编译器自动生成并维护的。虚表是属于类的,不属于某个具体的对象,一个类中只需要有一个虚表即可。同一个类中的所有对象使用同一个虚表,为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在每个对象的头添加了一个指针,用来指向虚表,并且这个指针的值会自动被设置成指向类的虚表,每一个virtaul函数的函数指针存放在虚表中,如果是单继承,先将父类的虚表添加到子类的虚表中,然后子类再添加自己新增的虚函数指针,但是在VS编译器中我们通常看不到新添加的虚函数指针,是编译器故意将他们隐藏起来,如果是多继承,在子类中新添加的虚函数指针会存放在第一个继承父类的虚函数表中。

为什么要引入抽象类和纯虚函数?

为了方便使用多态特性,在很多情况下由基类生成对象是很不合理的,纯虚函数在基类中是没有定义的,要求在子类必须加以实现,这种包含了纯虚函数的基类被称为抽象类,不能被实例化,如果子类没有实现纯虚函数,那么它他也是一个抽象类。

虚函数和纯虚函数有什么区别?

从基类的角度出发,如果一个类中声明了虚函数,这个函数是要在类中实现的,它的作用是为了能让这个函数在他的子类中能被重写,实现动态多态。纯虚函数,只是一个接口,一个函数声明,并没有在声明他的类中实现。对于子类来说它可以不重写基类中的虚函数,但是他必须要将基类中的纯虚函数实现。虚函数既继承接口的同时也继承了基类的实现,纯虚函数关注的是接口的统一性,实现完全由子类来完成。

什么是多态?他有什么作用?

多态就是一个接口多种实现,多态是面向对象的三大特性之一。多态分为静态多态和动态多态。静态多态包含函数重载和泛型编程,进程多态是程序调用函数,编译器决定使用哪个可执行的代码块。静态多态是由继承机制以及虚函实现的,通过指向派生类的基类指针或者引用,访问派生类中同名重写成员函数。

多态的作用就是把不同子类对象都当作父类来看,可以屏蔽不同子类之间的差异,从而写出通用的代码,做出通用的编程,以适应需求的不断变化。

类成员函数的重载、覆盖和重写区别

重载和普通的函数重载一样。

覆盖则基类的函数要加virtual (这就是多态的实现)

如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏,子类就重写了这个函数

如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏,子类就重写了这个函数

你可能感兴趣的:(c++面试,多态)