继承和多态常见的面试问题
Posted 在下赵某人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了继承和多态常见的面试问题相关的知识,希望对你有一定的参考价值。
继承和多态常见的面试问题
1 概念查考
-
下面哪种面向对象的方法可以让你变得富有( )
A: 继承 B: 封装 C: 多态 D: 抽象 -
( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的
调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定 -
面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现
- 析:C明显错误。优先使用组合,而不是多态。
- 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化对象 B:声明纯虚函数的类是虚基类
C:子类必须实现基类的纯虚函数 D:纯虚函数必须是空函数
- 析:
B:菱形虚拟继承中,共享的基类对象称为“虚基类”。
C:子类不实例化对象就无需实现基类的纯虚函数。
D:纯虚函数是无需实现,而非必须是空函数。
- 关于虚函数的描述正确的是( )
A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B:内联函数不能是虚函数
C:派生类必须重新定义基类的虚函数 D:虚函数可以是一个static型的函数
- 析:
B:inline函数在调用的地方展开了,故inline函数没有地址。而虚函数需要把函数地址放进虚函数表中。
D:虚函数的调用关系:this -> vptr -> vtable ->virtual function。而static修饰的函数无this指针。
- 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
D:一个类的不同对象共享该类的虚表
- 析:
A:多重继承会有多个虚函数表,几重继承,就会有几个虚函数表。
C:虚表是在编译期间生成的
- 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B:A类对象和B类对象前4个字节存储的都是虚基表的地址
C:A类对象和B类对象前4个字节存储的虚表地址相同
D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
- 析:
B:存储的都是虚函数表指针,而不是虚基表。虚基表是菱形虚拟继承那部分的概念。虚基表中存了偏移量。
- 下面程序输出结果是什么? ()
#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只会调用构造初始化一次。
- 多继承中指针偏移问题?下面说法正确的是( )
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
- 以下程序输出结果是什么()
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这种类型的。
参考答案:
- A 2. D 3. C 4. A 5. B 6. D 7. D 8. A 9. C 10. B
2 面试题:
-
为什么析构函数建议设置为虚函数
父类的析构函数强力建议设置为虚函数,这样动态释放父类指针所指的子类对象时,能够达到析构的多态 -
构造时,先构造父类,再构造子类部分,因此父类和子类的构造函数都会被调用;
-
析构时,要分情况:
(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指针,故不会报错。
-
为何static成员函数不能为virtual
-
虚函数依靠vptr和vtable来处理。vptr是一个指向虚函数表vtable的指针。因为vptr是类的成员,故需要用this指针来访问它,而静态成员函数没有this指针。所以static函数不能为virtual。
-
虚函数的调用关系:this -> vptr -> vtable ->virtual function
-
内联函数可以是虚函数吗?
答:
语法上:不可以,因为:inline函数在调用的地方展开了,故inline函数没有地址。而虚函数需要把函数地址放进虚函数表中。
实际中:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。(vs下是这样,其他编译器请自行验证) -
构造函数可以是虚函数吗?
答:不能,因为: -
具体描述:如果构造函数是虚函数,那么调用构造函数前先要用虚表指针找到虚表,而此时对象中虚表指针都还没有初始化,因为对象中的虚表指针是在构造函数初始化列表阶段才初始化的。
-
简而言之:构造的时候才初始化虚表指针,而如果构造函数是虚函数,则构造前就要用到虚表指针,互相矛盾。
-
对象访问普通函数快还是虚函数更快?
答:如果是普通对象,是一样快的,因为不满足多态调用的两个条件,会像普通函数那样调用而不会走多态调用的路子。如果是对象指针或者是对象引用,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。 -
虚函数表是在什么阶段生成的,存在哪的?
答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
以上是关于继承和多态常见的面试问题的主要内容,如果未能解决你的问题,请参考以下文章