一篇文章拿捏继承多态练习题

选择题

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

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

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

  4. 以下关于纯虚函数的说法,正确的是( )
    A:声明纯虚函数的类不能实例化对象 B:声明纯虚函数的类是虚基类
    C:子类必须实现基类的纯虚函数 D:纯虚函数必须是空函数

  5. 关于虚函数的描述正确的是( )
    A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B:内联函数不能是虚函数
    C:派生类必须重新定义基类的虚函数 D:虚函数可以是一个static型的函数

  6. 关于虚表说法正确的是( )
    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类使用的不是同一张虚表

  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<<s3<<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

  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
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: 以上都不正确

  参考答案:
   1. A   2. D   3. C   4. A   5. B
   6. D   7. D   8. A   9. C   10. B

解释

  1. 简单来说就是富二代。
    继承可以继承父类的成员和方法;封装就是屏蔽内部细节,只留下接口;多态是一个函数多种状态,虽然有很多状态,但是别的状态终究是别人的;抽象就是一种简化复杂问题实现的途径。

  2. 此题考查的是动态绑定的知识点。
    “模板(template)”是面向对象技术的一种参数化类。多态的“多态性”使作用于不同的对象的同一个操作可以有不同的解释,从而产生不同的执行结果。“动态绑定(dynamic binding)”是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。

  3. 考察继承和组合的知识点
    优先使用组合,因为继承破坏了父类的封装性。我们要求低耦合高内聚,因此优先组合。

  4. 考察纯虚函数的概念
    A.是对的;B.虚基类是菱形继承的知识点,不要搞混;C.如果没有实例化,子类可以不实现基类的纯虚函数,若子类需要实例化,则必须实现基类的纯虚函数;D.纯虚函数里有内容仍然可以编过。

  5. 考察虚函数的概念
    A. 要想构成重写,派生类的虚函数与基类的虚函数必须具有相同的参数,函数名和返回值!
    B.内联函数一定不能是虚函数,因为内联函数在编译期就确定地址了(进入不了虚表)。但是使用inline修饰的虚函数,是可以编过的,因为inline只是建议,编译器可以不把它当成内联。
    C.派生类可以不重新定义,使用父类的虚函数。重新定义就会把相应的地址,覆盖成自己定义的虚函数地址。
    D. 虚函数是为了构成多态才产生的,就是具体的对象调用相应的虚函数。static函数,仅仅通过类名就可以调用,很显然就破坏了多态的意义。并且static函数也没有this指针,进入不了虚表。因此虚函数不能是static的类型的。

  6. 考察虚表的知识点。
    A.多继承下,C继承A和B,就会有两张虚表。(前提是A和B都有虚函数)。
    B.子类会在构造函数的列表初始化虚表(复制父类的虚表)。如果没有重写,就和父类的一摸一样,但是也是两张表!
    C.虚表在编译期就已经生成了。
    D.每个类都会有一张虚表,所有的对象共享一张虚表。
    在这里插入图片描述

  7. 虽然没重写,但是也是两张虚表。 虚基表是菱形继承(继承)那里的概念,不要弄混!

  8. 不管逻辑怎么复杂,按声明的顺序(即继承的顺序来初始化)。

  9. 逻辑看下图
    在这里插入图片描述
    10 .好题。。
    首先满足重写,满足父类的指针或引用去调用吗?满足!虽然指针是子类的B*,但是p去调用test的时候,test的类型是A*,会产生隐式类型转换。
    B不是继承下来test了吗?B里面也有test呀。对!但是B里面的test终究是继承下来的,只是继承下来的地址,它是A的成员函数,要通过A的this去寻找!因此满足父类的指针调用。 满足多态! 因为是B调用的,所以用B的函数。还要注意,我们重写的仅仅是实现(即括号里的代码),定义还要沿用父类的定义(包括缺省参数),因此打印出来的是B->1。
    在这里插入图片描述
    附加两个习题辅助理解:

**加粗样式**
在这里插入图片描述

简答题

  1. 什么是多态?
    多态分为静态多态和动态多态。静态的多态是指在编译期就已经确定的场景,即编译期程序就知道要调用谁了。动态的多态是指只有在程序运行时,才确定到底是调用哪个函数(虽然虚表在编译期就已经确定了,但是如果程序不运行,就不会查虚表,不会查虚表,自然就不知道要调用哪个函数了)。

  2. 什么是重载、重写(覆盖)、重定义(隐藏)?
    在这里插入图片描述

  3. 多态的实现原理?
    通过对象里面的虚函数指针->虚函数表->对应的虚函数。具体参考多态原理

  4. inline函数可以是虚函数吗?
    可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。

  5. 静态成员可以是虚函数吗?
    虚函数是为了构成多态才产生的,就是具体的对象调用相应的虚函数。static函数,仅仅通过类名就可以调用,很显然就破坏了多态的意义。并且static函数也没有this指针,进入不了虚表。因此虚函数不能是static的类型的。的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

  6. 构造函数可以是虚函数吗?
    答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的,先有构造函数,再有虚函数,因此构造函数不能进虚函数表。

  7. 拷贝构造和赋值函数可以是虚函数吗?
    派生类的构造、拷贝构造、赋值重载叫合成版本,意思是它初始化或者赋值时,父类调用父类的,子类调用子类的,它是合成的(一下自动调用父子两个方法)。但是虚函数是为了重写,意思是:要么调用父类,要么调用子类,是非父即子的关系。

  8. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
    答:可以,并且最好把基类的析构函数定义成虚函数。参考地址
    在这里插入图片描述

  9. 对象访问普通函数快还是虚函数更快?
    答:首先如果是普通对象调用,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。如图
    在这里插入图片描述

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

  11. C++菱形继承的问题?虚继承的原理?
    答:参考地址
    在这里插入图片描述

  12. 什么是抽象类?抽象类的作用?
    答:包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,必须靠子类重写来纯虚函数。另外抽象类体现出了接口继承关系。

额外习题

class A
{
public:
    virtual void f()
    {
        cout << "A::f()" << endl;
    }
};

class B : public A
{
private:
    virtual void f()
    {
        cout << "B::f()" << endl;
    }
};

int main()
{
    A* pa = (A*)new B;
    pa->f();
    return 0;
}

A.B::f()
B.A::f(),因为子类的f()函数是私有的
C.A::f(),因为强制类型转化后,生成一个基类的临时对象,pa实际指向的是一个基类的临时对象
D.编译错误,私有的成员函数不能在类外调用

解释:答案:A, 结论:父类是可以通过多态调用子类的私有成员函数。因为函数的定义和声明是在父类,而子类只是重写了方法,在父亲看来,这个函数是public的,即便是子类定义成了private。
因为pa是A*,调用函数时,使用的是A* this,想要调用A的f,因为是public可以调用,但是发现是B的地址,转而调用B的f(因为多态调用看的是地址)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值