C++ 多态(三)多继承关系的虚函数表与常见问题

5.单继承和多继承关系的虚函数表

5.1 多继承中的虚函数表

两个基类及他们的虚表如下

创建子类如下

class D : public B1, public B2
{
public:
    virtual void func1()
    {
        cout << "D::func1()" << endl;
    }

    virtual void func4()
    {
        cout << "D::func4()" << endl;
    }

    virtual void func5()
    {
        cout << "D::func5()" << endl;
    }

    int _d;
};

D有两个基类,第一部分从B1继承,第二部分从B2继承,第三部分为派生类新增加的部分

子类对象模型如下:每一行4字节,共20字节,不同颜色框对应其分别的地址

子类虚表中按顺序进行分析:

  1. 首先继承B1,此时虚表中如下

  1. 应为子类中Func1进行了重写,B1::func1()覆盖为D::func1。func1,2,3,4都在基类中有过,子类中还新增了func5,更新的虚表如下

  1. B2继承,子类重写了func4,新增func5只放在B1中,B2不会增加,更新虚表,此时B1,B2虚表是分开的,互不影响

我们打印2个虚表查看,证明

typedef void (*PVFT)();

void PrintB1VFT(B1& b, const string& info)
{
    cout << info << endl;
    PVFT* pvft = (PVFT*)*(int*)&b;//找到虚表地址
    while (*pvft) //循环拿到虚表中的虚函数
    {
        (*pvft)();
        pvft++;
    }
    cout << "==================" << endl;
}

void PrintB2VFT(B2& b, const string& info)
{
    cout << info << endl;
    PVFT* pvft = (PVFT*)*(int*)&b; 
    while (*pvft)
    {
        (*pvft)();
        pvft++;
    }
    cout << "==================" << endl;
}

int main()
{
    cout << sizeof(D) << endl;

    D d;
    d._b1 = 1;
    d._b2 = 2;
    d._d = 3;

    PrintB1VFT(d, "B1 of D VFT:");
    PrintB2VFT(d, "B2 of D VFT:");

    return 0;
}

注意:在多继承中,子类新增加的虚函数地址放到第一张虚表后

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

6.1 概念查考

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. 下面程序输出结果是什么? ()

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

理论分析:

分析结果如下

上述调用分析没错,但是,classA只存储一份,基类A的构造方法只执行一次,在之后BCD执行时,A便不在执行(上图中,在D位置内,进入发现需要构造BCA,但是A是基类只有一份,所以首先直接执行A,之后在BC中遇到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

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

P->test,子类没有,只能找基类A,A中有func函数

参考答案:

1. A 2. D 3. C 4. A 5. B

6. D 7. D 8. A 9. C 10. B

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值