继承和多态常见的面试问题

Posted 在下赵某人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了继承和多态常见的面试问题相关的知识,希望对你有一定的参考价值。

继承和多态常见的面试问题

1 概念查考

  1. 下面哪种面向对象的方法可以让你变得富有( )
    A: 继承 B: 封装 C: 多态 D: 抽象

  2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的
    调用则可以关联于具体的对象。
    A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定

  3. 面向对象设计中的继承和组合,下面说法错误的是?()
    A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
    B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
    C:优先使用继承,而不是组合,是面向对象设计的第二原则
    D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现

  • 析:C明显错误。优先使用组合,而不是多态。
  1. 以下关于纯虚函数的说法,正确的是( )
    A:声明纯虚函数的类不能实例化对象 B:声明纯虚函数的类是虚基类
    C:子类必须实现基类的纯虚函数 D:纯虚函数必须是空函数
  • 析:
    B:菱形虚拟继承中,共享的基类对象称为“虚基类”。
    C:子类不实例化对象就无需实现基类的纯虚函数。
    D:纯虚函数是无需实现,而非必须是空函数
  1. 关于虚函数的描述正确的是( )
    A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B:内联函数不能是虚函数
    C:派生类必须重新定义基类的虚函数 D:虚函数可以是一个static型的函数
  • 析:
    B:inline函数在调用的地方展开了,故inline函数没有地址。而虚函数需要把函数地址放进虚函数表中。
    D:虚函数的调用关系:this -> vptr -> vtable ->virtual function。而static修饰的函数无this指针。
  1. 关于虚表说法正确的是( )
    A:一个类只能有一张虚表
    B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
    C:虚表是在运行期间动态生成的
    D:一个类的不同对象共享该类的虚表
  • 析:
    A:多重继承会有多个虚函数表,几重继承,就会有几个虚函数表。
    C:虚表是在编译期间生成的
  1. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
    A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
    B:A类对象和B类对象前4个字节存储的都是虚基表的地址
    C:A类对象和B类对象前4个字节存储的虚表地址相同
    D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
  • 析:
    B:存储的都是虚函数表指针,而不是虚基表。虚基表是菱形虚拟继承那部分的概念。虚基表中存了偏移量。
  1. 下面程序输出结果是什么? ()
#include<iostream>
using namespace std;
class A
public:
 A(char *s)  cout<<s<<endl; 
 ~A()
;
class B:virtual public A 
public:
 B(char *s1,char*s2):A(s1)  cout<<s2<<endl; 
;
class C:virtual public A 
public:
 C(char *s1,char*s2):A(s1)  cout<<s2<<endl;
;
class D:public B,public C 
public:
 D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1)
  cout<<s4<<endl;
;
int main() 
 D *p=new D("class A","class B","class C","class D");
 delete p;
 return 0; 

A:class A class B class C class D B:class D class B class C class A
C:class D class C class B class A D:class A class C class B class D

  • 析:
    初始化子类前会先调用父类构造初始化父类,多继承会按父类继承的顺序进行构造。因为编辑器的优化,菱形虚拟继承公共的父类A只会调用构造初始化一次。
  1. 多继承中指针偏移问题?下面说法正确的是( )
class Base1  public: int _b1; ;
class Base2  public: int _b2; ;
class Derive : public Base1, public Base2  public: int _d; ;
int main()
 Derive d;
 Base1* p1 = &d;
 Base2* p2 = &d;
 Derive* p3 = &d;
 return 0; 

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

  1. 以下程序输出结果是什么()
class A 
public:
 virtual void func(int val = 1) std::cout<<"A->"<< val <<std::endl;
 virtual void test() func();
;
class B : public A 
public:
 void func(int val=0) std::cout<<"B->"<< val <<std::endl; 
;
int main(int argc ,char* argv[])

 B*p = new B;
 p->test();
 return 0; 

A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

  • 析:
    子类对象调用test,会调用到从父类继承下来的test。test里面会用父类的this指针调用fun函数,且父类的fun完成了虚函数重写。此时构成多态,调用到父类的fun函数。父类继承子类时,普通函数为实现继承,而虚函数继承为接口继承。故用了父类fun的接口,子类fun的实现。故val的缺省值为1,故结果为:B->1。
    调父类的成员函数,父类成员函数里面的this指针是父类指针。成员函数中this指针的类型,始终都是 T* const这种类型的。

参考答案:

  1. A 2. D 3. C 4. A 5. B 6. D 7. D 8. A 9. C 10. B

2 面试题:

  1. 为什么析构函数建议设置为虚函数
    父类的析构函数强力建议设置为虚函数,这样动态释放父类指针所指的子类对象时,能够达到析构的多态

  2. 构造时,先构造父类,再构造子类部分,因此父类和子类的构造函数都会被调用;

  3. 析构时,要分情况:

(1) (父类析构函数不是虚函数)使用父类指针指向子类对象,析构该子类对象时,只会调用父类析构函数,因为不具多态性;代码理解:

class child:public parent;
parent *p = new child();  //父类指针指向子类对象
delete p;  //用父类指针调用,且未构成多态,调用的是父类析构函数,子类析构函数不会被调用

(2)上面存在一个问题,如果子类也分配的资源就会导致无法释放,使用虚函数可以解决上述问题。

class parent
	virtual	~parent();
;
class child:public parent

	~child();
;
parent *p = new child();  //父类指针指向子类对象
delete p;   //用父类指针调用,但构成多态,调用的是子类的析构函数。
            //子类析构函数会先析构子类部分,再调用父类的析构函数析构父类部分

(3)对于第一种非虚函数的情况,如果是用子类指针指向子类对象,那么会先调用子类析构函数,再调用父类析构函数,子类释放子类中分配的,父类分配父类中分配的。

class child:public parent;
child*p = new child();  //子类指针指向子类对象
delete p;   //虽然未构成多态,但用子类指针调用,调用的是子类的析构函数。
            //子类析构函数会先析构子类部分,再调用父类的析构函数析构父类部分

第三种方法也能够让父类和子类的资源都释放掉,使用virtual是为了使父类指针具有通用性
2. 多态调用的条件

  • 在继承中要构成多态还有两个条件:
    1. 必须通过基类的指针或者引用调用虚函数
    2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

  • 单说只构成多态调用
    1. 只要通过指针或者引用调用,调用的是虚函数即可,无需完成重写。入下例:

  • 首先,p1会使程序奔溃,单p2调用则正常运行。

  • 因为:类的成员函数存在公共代码段,空指针调用成员函数只要未涉及this指针即可正常运行。
    p1是指针调用,调用的是虚函数,所以是多态调用。多态调用要到对象中找虚函数表指针,要用到this指针,故会报错。
    p2是普通调用,无需this指针,且成员函数test中也未用到this指针,故不会报错。

  1. 为何static成员函数不能为virtual

  2. 虚函数依靠vptr和vtable来处理。vptr是一个指向虚函数表vtable的指针。因为vptr是类的成员,故需要用this指针来访问它,而静态成员函数没有this指针。所以static函数不能为virtual。

  3. 虚函数的调用关系:this -> vptr -> vtable ->virtual function

  4. 内联函数可以是虚函数吗?
    答:
    语法上:不可以,因为:inline函数在调用的地方展开了,故inline函数没有地址。而虚函数需要把函数地址放进虚函数表中。
    实际中:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。(vs下是这样,其他编译器请自行验证)

  5. 构造函数可以是虚函数吗?
    答:不能,因为:

  6. 具体描述:如果构造函数是虚函数,那么调用构造函数前先要用虚表指针找到虚表,而此时对象中虚表指针都还没有初始化,因为对象中的虚表指针是在构造函数初始化列表阶段才初始化的。

  7. 简而言之:构造的时候才初始化虚表指针,而如果构造函数是虚函数,则构造前就要用到虚表指针,互相矛盾。

  8. 对象访问普通函数快还是虚函数更快?
    答:如果是普通对象,是一样快的,因为不满足多态调用的两个条件,会像普通函数那样调用而不会走多态调用的路子。如果是对象指针或者是对象引用,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

  9. 虚函数表是在什么阶段生成的,存在哪的?
    答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

以上是关于继承和多态常见的面试问题的主要内容,如果未能解决你的问题,请参考以下文章

继承和多态常见的面试问题

继承和多态常见的问题

9-5:C++多态之多态和继承的经典题目以及面试中常考内容

C++多态

Java面试题

面试笔试Java常见面试笔试总结