目录

1、静态绑定和动态绑定的介绍

2、C语言struct和C++struct区别

3、虚函数可以声明为inline吗?

4、介绍 C++ 所有的构造函数

5、 什么情况下会调用拷贝构造函数

6、 为什么拷贝构造函数必须是引用传递,不能是值传递?

7、 何时需要合成构造函数

8、 何时需要合成复制构造函数

9、 C++类的虚函数表和虚函数在内存中的位置

10、 同一个类,实例化多次,是否共享虚函数表?

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

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

一、初始化方式

13、 成员列表初始化?

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

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

16、 析构函数的作用,如何起作用?

17、 构造函数和析构函数可以调用虚函数吗,为什么

18、 构造函数析构函数可以调用虚函数吗?

19、 构造函数的执行顺序?析构函数的执行顺序?构造函数内部干了啥?拷贝构造干了啥?

20、 虚析构函数的作用,父类的析构函数是否要设置为虚函数?


1、静态绑定和动态绑定的介绍

只有在存在虚函数,并发生继承时,基类指针或引用指向派生类对象时,才存在动态绑定。此时通过基类指针调用成员函数的话,如果调用的是虚函数,则执行派生类中的函数,如果不是虚函数,则调用基类的函数。

1.对象的静态类型:对象在声明时采用的类型。是在编译期确定的。

2.对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。

3.静态绑定:绑定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译期。

4.动态绑定:绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在运行期。

2、C语言struct和C++struct区别

1、C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)。

2、C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数。

3、C++中,struct的成员默认访问说明符为public(为了与C兼容),class中的默认访问限定符为private,struct增加了访问权限,且可以和类一样有成员函数。

4、struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名

3、虚函数可以声明为inline吗?

不能。

1、虚函数用于实现运行时的多态,或者称为静态绑定或动态绑定。而内联函数用于提高效率。内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。内联函数对于程序中需要频繁使用和调用的小函数非常有用。

2.虚函数要求在运行时进行类型确定,而内联函数要求在编译期完成相关的函数替换;

4、介绍 C++ 所有的构造函数

类的对象被创建时,编译系统为对象分配内存空间,并⾃动调⽤构造函数,由构造函数完成成员的初始化⼯作。

即构造函数的作⽤:初始化对象的数据成员。

1、⽆参数构造函数:即默认构造函数,如果没有明确写出⽆参数构造函数,编译器会⾃动⽣成默认的⽆参数构造函数,函数为空,什么也不做,如果不想使⽤⾃动⽣成的⽆参构造函数,必需要⾃⼰显示写出⼀个⽆参构造函数。

2、⼀般构造函数:也称重载构造函数,⼀般构造函数可以有各种参数形式,⼀个类可以有多个⼀般构造函数,前提是参数的个数或者类型不同,创建对象时根据传⼊参数不同调⽤不同的构造函数。

3、拷⻉构造函数:拷⻉构造函数的函数参数为对象本身的引⽤,⽤于根据⼀个已存在的对象复制出⼀个新的该类的对象,⼀般在函数中会将已存在的对象的数据成员的值⼀⼀复制到新创建的对象中。如果没有显示的写拷⻉构造函数,则系统会默认创建⼀个拷⻉构造函数,但当类中有指针成员时,最好不要使⽤编译器提供的默认的拷⻉构造函数,最好⾃⼰定义并且在函数中执⾏深拷⻉。

4、类型转换构造函数:根据⼀个指定类型的对象创建⼀个本类的对象,也可以算是⼀般构造函数的⼀种,这⾥提出来,是想说有的时候不允许默认转换的话,要记得将其声明为 explict 的,来阻⽌⼀些隐式转换的发⽣。

5、赋值运算符的重载:注意,这个类似拷⻉构造函数,将=右边的本类对象的值复制给=左边的对象,它不属于构造函数,=左右两边的对象必需已经被创建。如果没有显示的写赋值运算符的重载,系统也会⽣成默认的赋值运算符,做⼀些基本的拷⻉⼯作。

这⾥区分

A a1, A a2; a1 = a2;//调⽤赋值运算符

A a3 = a1;//调⽤拷⻉构造函数,因为进⾏的是初始化⼯作, a3 并未存在

5、 什么情况下会调用拷贝构造函数

类的对象需要拷⻉时,拷⻉构造函数将会被调⽤,以下的情况都会调⽤拷⻉构造函数:

1、⼀个对象以值传递的⽅式传⼊函数体,需要拷⻉构造函数创建⼀个临时对象压⼊到栈空间中。

2、⼀个对象以值传递的⽅式从函数返回,需要执⾏拷⻉构造函数创建⼀个临时对象作为返回值。

3、⼀个对象需要通过另外⼀个对象进⾏初始化。

6、 为什么拷贝构造函数必须是引用传递,不能是值传递?

为了防止递归调用。

当⼀个对象需要以值⽅式进⾏传递时,编译器会⽣成代码调⽤它的拷⻉构造函数⽣成⼀个副本,如果类A的拷⻉构造函数的参数不是引⽤传递,⽽是采⽤值传递,那么就⼜需要为了创建传递给拷⻉构造函数的参数的临时对象,⽽⼜⼀次调⽤类 A 的拷⻉构造函数,这就是⼀个⽆限递归。

1) 拷贝构造函数的作用就是用来复制对象的,再使用这个对象的实例来初始化这个对象的一个新的实例。

2) 参数传递过程到底发生了什么?将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!

i)值传递:

对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);

对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj); 首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用

ii)引用传递:

无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型)。

上述1) 2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用,内存溢出。

拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归。

7、 何时需要合成构造函数

1.如果一个类没有任何构造函数,但他含有一个成员对象,该成员对象含有默认构造函数,那么编译器就为该类合成一个默认构造函数,因为不合成一个默认构造函数那么该成员对象的构造函数不能调用;

2.没有任何构造函数的类派生自一个带有默认构造函数的基类,那么需要为该派生类合成一个构造函数,只有这样基类的构造函数才能被调用;

3.带有虚函数的类,虚函数的引入需要进入虚表,指向虚表的指针,该指针是在构造函数中初始化的,所以没有构造函数的话该指针无法被初始化;

4.带有一个虚基类的类

5.并不是任何没有构造函数的类都会合成一个构造函数

6.编译器合成出来的构造函数并不会显示设定类内的每一个成员变量

8、 何时需要合成复制构造函数

有三种情况会以一个对象的内容作为另一个对象的初值:

1.对一个对象做显示的初始化操作,X xx = x;

2.当对象被当做参数交给某个函数时;

3.当函数传回一个类对象时;

9、 C++类的虚函数表和虚函数在内存中的位置

C++类对象中虚函数表指针、虚函数表、虚函数之间的关系以及在内存中的布局_子木呀的博客-CSDN博客

关系:虚函数表指针(保存在堆或栈)->虚函数表(常量区 .rodata)->虚函数(代码段 .text)

虚函数表指针是虚函数表所在位置的地址。虚函数表指针属于对象实例。因而通过new 出来的对象的虚函数表指针位于堆,声名对象的虚函数表指针位于栈。

总结:

1、虚函数表指针位置取决于对象在哪。如果是new的对象,则存在堆上,如果是直接声明,则存在栈上。

2、虚函数表位于只读数据段(.rodata),即:C++内存模型中的常量区;

3、虚函数代码则位于代码段(.text),也就是C++内存模型中的代码区。

10、 同一个类,实例化多次,是否共享虚函数表?

所有实例是共用一个虚函数表。

1.为什么要共用同一个虚函数表?可能是为了节省内存吧,首先同一个类的对象虚函数都是一样的,没必要重新伴随构造生成一份一模一样的表,所以拷贝虚函数表的表地址就行。

2.C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——假设有多层继承或是多重继承的情况下)。

3.这意味着我们通过对象实例的地址得到这张虚函数表。然后就能够遍历当中函数指针,并调用对应的函数。

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

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

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

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

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

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

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

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

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

一、初始化方式

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

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

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

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

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

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

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

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

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

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

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

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

13、 成员列表初始化?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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虚析构函数。

16、 析构函数的作用,如何起作用?

1.构造函数只是起初始化值的作用,但实例化一个对象的时候,可以通过实例去传递参数,从主函数传递到其他的函数里面,这样就使其他的函数里面有值了。规则,只要你实例化一个对象,系统自动会调用一个构造函数,就是你不写,编译器也自动调用一次。

2.析构函数与构造函数的作用相反,用于撤销对象的一些特殊任务处理,可以是释放对象分配的内存空间;特点:析构函数与构造函数同名,但该函数前面加~。

3.析构函数没有参数,也没有返回值,而且不能重载,在一个类中只能有一个析构函数。当撤销对象时,编译器也会自动调用析构函数。每一个类必须有一个析构函数,用户可以自定义析构函数,也可以是编译器自动生成默认的析构函数。一般析构函数定义为类的公有成员。

17、 构造函数和析构函数可以调用虚函数吗,为什么

1.在C++中,提倡不在构造函数和析构函数中调用虚函数;

2.构造函数和析构函数调用虚函数时都不使用动态联编,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本;

3.因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化,因此调用子类的虚函数时不安全的,故而C++不会进行动态联编;

4.析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经销毁,这个时候再调用子类的虚函数没有任何意义。

5.继承类在构造的时候总是首先调用其基类的构造函数来对属于其基类的部分进行构造,在这个时候,整个类被当作基类来处理,继承类的部分对整个类来说好像不存在一样,直到基类的构造函数退出并进入继承类的构造函数,该类才被当作继承类来出来处理。对析构也一样,只是析构的顺序正好相反。

18、 构造函数析构函数可以调用虚函数吗?

1.在构造函数最好不要调用虚函数;

2.构造函数或者析构函数调用虚函数并不会发挥虚函数动态绑定的特性,跟普通函数没区别;

3.即使构造函数或者析构函数如果能成功调用虚函数,程序的运行结果也是不可控的。

lsy注:当有基类指针指向派生类时,需要将析构函数定义为虚函数。

19、 构造函数的执行顺序?析构函数的执行顺序?构造函数内部干了啥?拷贝构造干了啥?

1、构造函数顺序

1.基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。

2.成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。

3.派生类构造函数。

2、析构函数顺序

1.调用派生类的析构函数;

2.调用成员类对象的析构函数;

3.调用基类的析构函数。

20、 虚析构函数的作用,父类的析构函数是否要设置为虚函数?

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

2.纯虚析构函数一定得定义,因为每一个派生类析构函数会被编译器加以扩张,以静态调用的方式调用其每一个虚基类以及上一层基类的析构函数。因此,缺乏任何一个基类析构函数的定义,就会导致链接失败。因此,最好不要把虚析构函数定义为纯虚析构函数。

二十万字C/C++、嵌入式软开面试题全集宝典二相关推荐

  1. 二十万字C/C++、嵌入式软开面试题全集宝典七

    目录 121. 怎样判断两个浮点数是否相等? 122. 宏定义一个取两个数中较大值的功能 123. define.const.typedef.inline使用方法? 124. printf实现原理? ...

  2. 二十万字C/C++、嵌入式软开面试题全集宝典十一

    目录 1. 红黑树的性质 2. 红黑树的插入与旋转 3. 红黑树与平衡二叉树 4. 二叉平衡树.红黑树.B树.B+树的区别与联系 5. hello world 程序开始到打印到屏幕上的全过程? 1. ...

  3. 二十万字C/C++、嵌入式软开面试题全集宝典八

    目录 141. 迭代器++it,it++哪个好,为什么 142. C++如何处理多个异常的? 143. 模板和实现可不可以不写在一个文件里面?为什么? 144. 在成员函数中调用delete this ...

  4. 二十万字C/C++、嵌入式软开面试题全集宝典五

    目录 81. vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间? 82. map[]与find的区别? 83. STL中list与queue之间的区别 84. STL ...

  5. 二十万字C/C++、嵌入式软开面试题全集宝典四

    目录 1. 虚函数的代价? 2. 用C语言实现C++的继承 3. 继承机制中对象之间如何转换?指针和引用之间如何转换? 4. C++四种类型转换 5. 为什么要用static_cast转换而不用c语言 ...

  6. 二十万字C/C++、嵌入式软开面试题全集宝典三

    目录 1. 构造函数析构函数可否抛出异常 2. 类如何实现只能静态分配和只能动态分配 3. 如果想将某个类用作基类,为什么该类必须定义而非声明? 4. 什么情况会自动生成默认构造函数? 5. 构造函数 ...

  7. 二十万字C/C++、嵌入式软开面试题全集宝典十

    目录 1. 编码实现某一变量某位清0或置1 2. 分别写出BOOL,int,float,指针类型的变量a 与"零"的比较语句. 3. 局部变量全局变量的问题? 4. 数组和指针的区 ...

  8. 二十万字C/C++、嵌入式软开面试题全集宝典九

    目录 161. 空类 162. 空类会默认添加哪些东西?怎么写? 163. 标准库是什么? 164. const char* 与string之间的关系,传递参数问题? 165. char * 与cha ...

  9. 二十万字C/C++、嵌入式软开面试题全集宝典六

    目录 101. 字节对齐有什么作用? 102. C语言中#pragma用法 103. new和malloc的区别? 104. malloc/calloc/realloc三者之间的区别? 105. de ...

最新文章

  1. Linux awk 命令 说明
  2. unicode和ansi(ascii转换)
  3. PL/SQL数据库字符集问题
  4. 性能测试——房屋租赁系统(Badboy、JMeter)
  5. 三个对CS最大的谬误
  6. 【NOI2013】树的计数【树的遍历】【期望】
  7. python快递代取系统_代取快递的变现方式,校园跑腿的经营范围有多大?
  8. 面对10ms的延迟,这群人就是一个字“改”
  9. SQL--合计函数(Aggregate functions):avg,count,first,last,max,min,sum
  10. ijkplayer-音视频变速播放实现
  11. 史上最完美将windows键盘映射成mac键盘,绝对不需要买HHKB了
  12. 什么是事件流? 什么是事件冒泡? 什么是事件捕获?
  13. 视频转换gif动图 - 在线gif制作工具
  14. Android4.4添加系统服务(aidl接口服务)
  15. 计算机应用基础的重点知识,《计算机应用基础知识》重点总结
  16. 任务三、学生喂养三种宠物:猫、狗和鸟
  17. 追寻绿色世界、草木清香
  18. python读取串口数据 绘图_使用Python串口实时显示数据并绘图的例子
  19. 给飞机叶片穿戴上3D打印传感器
  20. MacW资讯:开启Mac的壁纸自动更换功能

热门文章

  1. RTS寻路相关资料收集
  2. 毕业生的商业软件开发之路 --- C#基本语法-C#代码模块逻辑框架
  3. 简单介绍Kubernetes
  4. jvm - 垃圾回收 gc
  5. QA seven's blog
  6. 2、xen虚拟机的安装
  7. Memcached - In Action
  8. xen虚拟化实战系列(一)之xen虚拟化环境安装
  9. 归纳几点html编码要素--杜绝浏览器不兼容
  10. hdu 3006(状态压缩)