大家好,接下来为大家带来一些关于继承与多态的常见面试题,希望大家多多支持哦~。
一、概念考察
1. 下面哪种面向对象的方法可以让你变得富有( )
A: 继承 B: 封装 C: 多态 D: 抽象
解析:这里是一个双关语题目。虽然在面向对象编程中,“继承”是指子类继承父类的属性和方法,但在日常生活中,“继承”也指继承财产。因此,选择“继承”有双重含义,可以理解为变得富有。
因此这道题应该选A.
2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,
而对方法的调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定
解析:动态绑定是指在程序运行时确定调用哪个方法。这样的方法调用与具体的对象相关,而方法的定义与具体对象无关。这使得可以在运行时决定调用哪个具体对象的方法。
所以应该选D。
3. 面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复
用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动
态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封
装性的表现
解析:实际的面向对象设计原则推荐我们在日常中优先使用组合,而不是继承。因为组合比继承更加的灵活,避免了继承所带来的高耦合问题。在某种程度上,继承也被认为是破坏了父类封装。
所以这道题应该选C。
4. 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化对象 B:声明纯虚函数的类是虚基类
C:子类必须实现基类的纯虚函数 D:纯虚函数必须是空函数
解析:B:子类也可以声明纯虚函数。C:子类只要不实例化对象,就可以不实现基类的纯虚函数。D:纯虚函数不用是空函数。
所以这道题应该选A.
5. 关于虚函数的描述正确的是( )
A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B:内联函数不能是虚函数
C:派生类必须重新定义基类的虚函数 D:虚函数可以是一个static型的函数
解析:A:派生类的虚函数参数个数与类型必须要保持和基类一致。C:派生类可以选择是否对基类虚函数进行重新定义。D:虚函数不能是static类型的函数,因为虚函数依赖于对象实例,而static不依赖对象实例。
所以这道题应该选B。
6. 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
D:一个类的不同对象共享该类的虚表
解析:A:一个类可以有多个虚表,尤其是在多重继承的情况下。B:无论是否重写基类的虚函数,子类与基类的虚表也不会相同,可以理解为派生类复制了一份基类的虚表,并对这个虚表进行一下补充和修改。C:虚表是在编译期间就生成了的,不是在运行期间。
所以这道题应该选D。
7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B:A类对象和B类对象前4个字节存储的都是虚基表的地址
C:A类对象和B类对象前4个字节存储的虚表地址相同
D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
解析:A:两个类的对象前四个字节都是虚表的地址,这才方便实现多态。B:虚基表不是虚表,虚基表用于解决多重继承中的虚拟继承问题,而虚表用于实现多态。C:如果B类重写了A类的虚函数,即使B类没有定义其他虚函数,B类仍然会有自己的虚表。这是因为每个类在继承层次结构中会有自己独立的虚表,尽管它们可能包含相同的虚函数。
所以这道题应该选D。
8. 下面程序输出结果是什么? ()
#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
)会由最派生类(D
)的构造函数直接初始化,即由D
的构造函数来调用A
的构造函数。因此,A
的构造函数会在 D
构造函数的成员初始化列表中被调用。
D *p = new D("class A", "class B", "class C", "class D");
会依次执行:
A("class A")
被 D
的初始化列表中的 A(s1)
初始化,因此输出 "class A"。- 然后是
B
的构造函数 B("class A", "class B")
,其中 A(s1)
由于 A
已经初始化,不会再次输出 "class A"。接着输出 "class B"。 - 然后是
C
的构造函数 C("class A", "class C")
,同理,不会再次输出 "class A"。接着输出 "class C"。 - 最后,
D
的构造函数主体执行,输出 "class D"。
因此,构造函数部分的输出顺序是:class A class B class C class D
所以这道题应该选A.
9. 多继承中指针偏移问题?下面说法正确的是( )
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
解析;由代码可知Derive先后继承了Base1与Base2两个基类,所以对于一个Derive对象来说,他的内存大小应该是先包含了一个Base1大小,随后包含一个Base2大小,最后才Derive类型独自的空间。
所以对于p1指针来说,他就指向Base1大小的开始位置,对于p3来说,指向Derive大小的开始,所以p1与p3指向同一个字节。而p2是Base2类型的指针,根据基类指针指向派生类的规则,应该指向的是Base2那部分,因此p2不等于p1和p3.
所以这道题应该选C。
10.
以下程序输出结果是什么()
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:
以上都不正确
解析:A是B的父类,在B类进行实例化时,会先对父类A进行构造,由于缺省参数只会被初始化一次,所以val在A构造时就被初始化为1。随后B并没有对test进行重写,但对func进行了重写,这里用B类指针调用test就会调用B类的func,所以会打印出B->,由于val已经被初始化为1,所以就打印出1.最后结果为:B->1。
所以这道题选B。
二、问答题
1. 什么是多态?
答:去完成某个行为,当不同的对象去完成时会产生出不同的状态。
2. 什么是重载、重写(覆盖)、重定义(隐藏)?
答:
(1)、重载是指在同一个作用域中,多个函数具有相同的名字但参数列表不同(参数的类型、数量或顺序不同)。
(2)、重写是指在派生类中重新定义基类中已经存在的虚函数。重写要求函数名、参数列表和返回类型都必须相同,并且基类的函数必须是虚函数。
(3)、重定义是指在派生类中定义了一个与基类中同名的函数,但这个函数不是基类函数的重写,而是隐藏了基类中的同名函数。重定义通常发生在非虚函数或函数签名不匹配的情况下。
3. 多态的实现原理?
答:动态多态在运行时通过虚函数表(vtable)和虚函数指针(vptr)机制来实现。
虚函数表是一个指针数组,每个类都有一个自己的虚函数表。虚函数表中的每个指针指向该类的虚函数实现。
每个含有虚函数的对象都有一个隐藏的虚函数指针,指向该类的虚函数表。当对象的类型改变时,虚函数指针会指向相应类的虚函数表。
4. inline函数可以是虚函数吗?
答:可以,不过编译器就忽略inline属性,这个函数就不再是 inline,因为虚函数要放到虚表中去。
5. 静态成员可以是虚函数吗?
答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
6. 构造函数可以是虚函数吗?
答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
答:可以,并且最好把基类的析构函数定义成虚函数。当基类的析构函数是虚函数时,此时子类析构函数一定构成重写。
8. 对象访问普通函数快还是虚函数更快?
答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
9. 虚函数表是在什么阶段生成的,存在哪的?
答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
10. 什么是抽象类?抽象类的作用?
答:抽象类是指至少有一个纯虚函数的类。纯虚函数是一种没有实现的函数,必须由派生类提供实现。抽象类不能实例化,但可以用作基类
。抽象类强制重写了虚函数,规定了派生类必须实现的函数。这种方式确保了派生类具备统一的接口。通过定义纯虚函数,抽象类可以强制派生类实现特定的功能,确保派生类具有统一的行为。
另外抽象类体现出了接口继承关系。
希望以上的内容能够对大家有所帮助!谢谢!