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) ,虚继承关系产生虚基类,用于在多重继承下保证基类在子类中拥有唯一实例。

       

         

       

posted @ 2019-11-05 11:24  Duikerdd  阅读(4275)  评论(1编辑  收藏  举报