zoukankan      html  css  js  c++  java
  • C++ 多态详解及常见面试题

           什么是多态?

           多态就是不同对象对同一行为会有不同的状态(举例 : 学生和成人都去买票时,学生会打折,成人不会)

           实现多态有两个条件: 一是虚函数重写,重写就是用来设置不同状态的

                      二是对象调用虚函数时必须是指针或者引用

           ps:没有这两个条件无法构成多态,很多笔试题都会利用这个陷阱让你上当!

           实际上,代码上体现(动态)多态就是当父类指针指向子类对象,然后通过父类指针能调用子类的成员函数。

                  

              什么是虚函数?什么是重写?

           虚函数是带有virtual关键字的成员函数 

           子类有个和父类完全相同(函数名,形参,返回值都相同,协变和析构函数除外)虚函数,就称子类虚函数重写父类虚函数 

            

           多态的原理?

           多态是用虚函数表实现的。

           有虚函数的类都会生成一个虚函数表,这个表在编译时生成。

           虚函数表是一个存储虚函数地址的数组,以NULL结尾。

           如果要生成子类虚表,就要经过三个步骤:第一步,将父类虚表内容拷贝到子类虚表上;

                              第二步,将子类重写的虚函数覆盖掉表中父类的虚函数;

                              第三步,如果子类有新增加的虚函数,按声明次序加到最后

             多态如何调用?

           满足多态的函数调用,程序运行起来后,根据对象中的虚表指针来找实际应该调用的函数; 而不满足多态的函数在函数编译时就确定函数地址了。

                       

             动态绑定与静态绑定?

             静态绑定是程序编译时确定程序行为。

             动态绑定是程序运行时根据具体的对象确定程序行为。

      

            继承中的多态:

            单继承无虚函数覆盖: 虚函数按声明顺序存放,父类虚函数在子类虚函数前面.

                      

           单继承有虚函数覆盖: 覆盖的f()替代原有父类虚函数位置,没覆盖的不变

          

           多继承无虚函数覆盖: 每个父类都有自己的虚表,子类成员函数被放入第一个父类表中

             

           多继承有虚函数覆盖: 三个父类虚表中的f()都会被子类函数指针覆盖

             

               多继承规则: 多继承子类未重写的虚函数放在第一个继承父类部分的虚函数表中,继承的虚表都会覆盖

          

          重复继承: B类数据重复,具有二义性.

                

                  

          二义性举例: d.ib = 0;  x     d.B1::ib = 0; √

          菱形虚拟继承: 解决重复继承的数据重复,二义性问题.

              虚继承: 继承语法中加入virtual关键字.

                   虚继承子类: 加入新的虚函数,会生成一个虚函数指针(vptr)以及一张虚表,放在对象内存最前面.

                   普通继承子类: 加入新的虚函数,直接扩展父类虚表.

                      虚继承子类会单独保留父类的vptr和虚表,用一个四字节0分界.

                   虚继承子类有一个四字节指针偏移值.

              虚基类表:  虚继承会生成一个隐藏的虚基类指针(vbptr),虚基类指针总是在虚表指针之后.

                   vbptr指向虚基类表,虚基类表记录了vbptr到vptr的偏移值.

                   虚基类表实际上就是记录了虚表指针的位置.

                                  

            单虚继承下,vbptr记录了两个vptr所在位置的偏移值.(这里[4]的地址为007C FE00,打印粗心)

                              

            菱形虚拟继承举例:

                         

           内存结构:

                             

                           

           菱形虚拟继承内存分布总结:

          (1)基类出现顺序: B1(最左父类),B2(次左父类),B(虚祖父类)

          (2)D类数据成员在B类前,并以0x00分割

          (3)D类覆盖扩展原则与前面多继承规则一样

          (4)B类内容放到了最后

          如果出现菱形继承,B1有一个构造函数初始化B,B2也有一个构造函数初始化B,那么D类应该按谁的构造方式初始化B呢?   

                      

           答案是必须让D来初始化虚基类B.

           C++规定必须由最终的派生类D来初始化虚基类B,直接派生类 B1 和 B2 对 B 的构造函数的调用是无效的,并且虚基类始终优先调用,与声明位置无关.

           

           虚函数和虚表在哪里?

           虚函数和普通函数一样在代码段,vs2013测试下,虚表在只读常量区

                           

                            

           抽象类?

           有纯虚函数的类。

           纯虚函数就是虚函数后面再加上 = 0;

           体现了接口继承,只声明不实现 --- 比如,动物呼吸不能实现,但继承它的鱼和人都能呼吸并且呈现多态性

           final ---  让父类虚函数不能被重写  ---  体现实现继承

                   

             override ---  纯虚函数 + override --- 强制重写

               

          面试题:

           1.inline函数可以是虚函数吗?

           不能,因为inline函数没有地址,无法放到虚函数表中

           2.静态成员可以是虚函数吗?

             不能, 因为静态成员函数没有this指针, 因为有this指针才能访问到虚表指针,有虚表指针才能找到虚表从而调用实际应该调用的函数。

           3.构造函数可以是虚函数吗?/虚函数指针在什么时候生成的的?

              不能,因为对象中的虚表指针是在构造函数初始化列表阶段才初始化的

           4.析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

             可以,并且最好把基类的析构函数定义成虚函数

             当父类指针指向子类对象时,如果析构函数不是虚函数,析构就只会释放父类对象,造成内存泄漏。(因为析构重名,只能调用一个,调用默认的父类析构函数)

             定义成虚函数后,调用析构时就会取出虚表指针找到实际应该调用的函数(指针虽然都是父类类型,但是指针内取出的虚表是不一样的,所以析构能调用子类析构)

           5.对象访问普通函数快还是虚函数更快?

           首先如果是普通对象,是一样快的,如果是指针对象或者是引用对象,则调用的普通函数快,因为普通对象在编译时就确定地址了,虚函数构成多态,运行时调用虚函数需要到虚函数表中去查找 

            6.下面输出是?

    class B{};
    class B1 :public virtual  B{};
    class B2 :public virtual  B{};
    class D : public B1, public B2{};
    
    int main()
    {
        B b;
        B1 b1;
        B2 b2;
        D d;
        cout << "sizeof(b)=" << sizeof(b)<<endl;           //1,空类用了一个占位符
        cout << "sizeof(b1)=" << sizeof(b1) << endl;       //4,有虚基类表指针(32位系统)
        cout << "sizeof(b2)=" << sizeof(b2) << endl;       //4,同上
        cout << "sizeof(d)=" << sizeof(d) << endl;         //8,有b1和b2两个虚基类表指针
        return 0;
    }
    

            7.设计一个不能被继承的类

    //解法:私有一个辅助类,子类虚继承辅助类,并是他的友元.
    //       友元能够调用辅助类的私有函数
    //       因为有虚继承,所以Try类必须自己构造虚基类,但是虚基类私有了构造和析构,所以调用不了
    class MakeSealed{
        friend SealedClass;
     private:
         MakeSealed();
         ~MakeSealed();
    };
    
    class SealedClass : virtual public MakeSealed;
    {
     public:
         SealedClass();
         ~SealedClass();
    };
    
    class Try : public MakeSealed;
    {             
    };
    

      

         c++的多态总结:

         在父类的函数前加上virtual关键字,在子类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是子类,就调用子类的函数,如果对象类型是父类,就调用父类的函数。

      •   虚函数机制(virtual function) , 用以支持执行期绑定,实现多态。
      •   虚基类 (virtual base class) ,虚继承关系产生虚基类,用于在多重继承下保证基类在子类中拥有唯一实例。

           

             

           

  • 相关阅读:
    构建maven项目,自定义目录结构方法
    Nginx反向代理实现负载均衡以及session共享
    Spring Boot 2.x引入JS,CSS 失效问题
    WebMvcConfigurerAdapter已过时
    闲谈Tomcat性能优化
    oracle decode函数和 sign函数
    为什么要使用MQ和到底什么时候要使用MQ
    redis持久化的几种方式
    【mySQL】left join、right join和join的区别
    redis缓存在项目中的使用
  • 原文地址:https://www.cnblogs.com/Duikerdd/p/11761124.html
Copyright © 2011-2022 走看看