十八万字吐血整理的C/C++、嵌入式常见面试题宝典!!!!

欢迎订阅,希望能点个赞!!!!

正在持续更新!!!!!欢迎探讨!!!

完整专栏地址:https://blog.nowcoder.net/zhuanlan/gmPWX0

相关知识点都能零星在网上找到,这个文章系列将目前遇到的所有常见面试问题进行一个汇总。

文中很多资料避免不了从网上或是其他复习资料里收集整理,十分感谢前辈的辛勤付出,如果存在侵权请一定联系我进行删除

也有相当一部分是本人在经历提前批以及秋招的过程中遇到和验证过的。

系列文章PDF下载地址:《最全C_C++及嵌入式软开面试题宝典.pdf》



26、 编译器处理虚函数表应该如何处理

对于派⽣类来说,编译器建⽴虚函数表的过程其实⼀共是三个步骤:

1、拷⻉基类的虚函数表,如果是多继承,就拷⻉每个有虚函数基类的虚函数表

2、当⼀个基类的虚函数表和派⽣类⾃身的虚函数表是共用的⼀个虚函数表,也称这个基类为派⽣类的主基类

3、查看派⽣类中是否有重写基类中的虚函数,如果有,就替换成已经重写的虚函数地址;

4、查看派⽣类是否有⾃身的虚函数,如果有,就追加⾃身的虚函数到⾃身的虚函数表中。

Derived *pd = new D(); B *pb = pd; C *pc = pd; 其中 pb pd pc 的指针位置是不同的。

要注意的是派⽣类的⾃身的内容要追加在主基类的内存块后,虚函数表指针始终在内存最前面。

27、类成员初始化方式?构造函数的执行顺序?为什么用成员初始化列表会快一些?

一、初始化方式

1.赋值初始化,通过在函数体内进行赋值初始化;

2.列表初始化,在冒号后使用初始化列表进行初始化。

这两种方式的主要区别在于:

1.对于赋值初始化,是在所有的数据成员被分配内存空间后才进行的。

2.列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。

二、列表初始化比赋值初始化更快的原因

赋值初始化是在构造函数当中做赋值的操作,而列表初始化是做纯粹的初始化操作。我们都知道,C++的赋值操作是会产生临时对象的。临时对象的出现会降低程序的效率。

三、一个派生类构造函数的执行顺序如下:

1.虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。

2.基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。

3.类类型的成员对象的构造函数(按照初始化顺序)

4.派生类自己的构造函数。

28、成员列表初始化?

1.必须使用成员初始化的四种情况

①  当初始化一个引用成员时;

②  当初始化一个常量成员时;

③  当调用一个基类的构造函数,而它拥有一组参数时;

④  当调用一个成员类的构造函数,而它拥有一组参数时;

2.成员初始化列表做了什么

①  编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前;

②  list中的项目顺序是由类中的成员声明顺序决定的,不是由初始化列表的顺序决定的;

29、 何时需要成员初始化列表?过程是什么?

1.当初始化一个引用成员变量时;

2.初始化一个const成员变量时;

3.当调用一个基类的构造函数,而构造函数拥有一组参数时;

4.当调用一个成员类的构造函数,而他拥有一组参数;

5.编译器会一一操作初始化列表,以适当顺序在构造函数之内安插初始化操作,并且在任何显示用户代码前。list中的项目顺序是由类中的成员声明顺序决定的,不是初始化列表中的排列顺序决定的。

30、构造函数为什么不能为虚函数?析构函数为什么要虚函数?

1.从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的。问题出来了,假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

2.从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到相应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

3.构造函数不须要是虚函数,也不同意是虚函数,由于创建一个对象时我们总是要明白指定对象的类型,虽然我们可能通过实验室的基类的指针或引用去訪问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候假设析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。

4.从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,也没有必要成为虚函数。

5.当一个构造函数被调用时,它做的首要的事情之中的一个是初始化它的VPTR。因此,它仅仅能知道它是当前类的,而全然忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(由于类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。并且,仅仅要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但假设接着另一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的还有一个理由。可是,当这一系列构造函数调用正发生时,每一个构造函数都已经设置VPTR指向它自己的VTABLE。假设函数调用使用虚机制,它将仅仅产生通过它自己的VTABLE的调用,而不是最后的VTABLE(全部构造函数被调用后才会有最后的VTABLE)。

6.直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。