《C++面试宝典》V1.0 冲刺大厂~持续更新(1)

分享面试总结,涉及C++、算法、数据结构、操作系统、计算机网络、Linux、数据库、设计模式等后面持续更新~
内容多为一问一答式,多数来自收集。整理总结,视频、书籍学习所得,如有错误请指出,万分感谢!!!
学习建议:针对八股文,不太了解的可以网上扩展,自己总结,拿来主义最好能消化成自己的。
部分内容已更新,需要最新资料可私聊或者留下评论邮箱发给你~

《C++篇》 — √1

1. C和C++的区别?

1)        C是面向过程语言,C++是面向对象语言。通常简历上会写熟悉C/C++(切勿写精通),面试官会经常问:C和C++的区别?首先要知道什么是面向对象?即把事物对象化,包括属性和行为。接下来可引出C++三大特性,给面试官提问的机会:继承(提高代码复用性)、封装(隐藏对象属性和细节,对外提供访问方式)、多态(提供接口,扩展)。巴拉巴拉就能说一大堆。

2)        C和C++动态管理内存的方法不同。C是使用malloc/free库函数,而C++除此之外还可以使用new/delete关键字。(关于malloc/newfree/delete的区别,巴拉巴拉)。

3)        C中的结构体和C++的类的区别。类为C++独有的,类的相关知识点(巴拉巴拉),C没有,但C中的struct可以在C++中正常使用,且C++对struct进行了扩展,使struct在C++中可以和class一样作为类使用,但struct的成员默认访问修饰符是public,而class默认的是private。

4)        C++支持函数重载,C不支持函数重载。C++支持重载的依据是C++的名字修饰与C不同,例如在C++中函数int fun(int, int)经过名字修饰之后变为_fun_int_int,而C是_fun,所以C++能支持不同的参数调用不同的函数。

5)        C++中有引用,而C没有。可引出:引用和指针的区别(巴拉巴拉)。

6)        内外链接区别。C++全部变量的默认链接属性是外链接,而C是内连接。可引出内外链接区别(巴拉巴拉)。

7)        关键字:如const,C语言中的const:被修饰后不能做左值,定义的时候必须初始化,可以用作数组的下标。const在C++中的编译规则是替换(和宏很像)。

8)        局部变量的声明规则不同,C++特有输入输出流,作用域等,请自行网上扩展。



2. C++中的指针参数传递和引用参数传递?

1) 值传递是最普通的传递方式,如函数定义为fun(int a),在调用的地方如int x=6,使用fun(x)即可。这种方式在fun(int a)函数内部的对a的修改不能导致外部x的变化。
2) 指针传递是地址传递,函数定义为fun(int *a),形参为指针,这就要求调用的时候传递进去一个参数的地址,例如int x=6; fun(&x)。 这种方式在fun(int a)函数内部的对a的修改能导致外部x的变化。
3) 引用传递只有C++支持,引用传递函数定义为fun(int &a),这里&符号是引用而不是取地址的意思,调用方式和值传递类似,例如int x=6; fun(x)。但是这种方式在fun(int a)函数内部的对a的修改能导致外部x的变化。
理解:
(1)指针参数传递本质上是值传递,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。
(2)引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。
(3)引用传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使用指向指针的指针或者指针引用。
(4)从编译的角度来讲,程序在编译时分别将指针和引用添加到符号表上,符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值(与实参名字不同,地址相同)。符号表生成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

3. 引用和指针的区别?

1)        本质:指针是一个实体,需要分配内存空间。引用只是变量的别名,无需分配内存空间。

2)        内存分配上:程序为指针变量分配区域,而不为引用分配内存区域。引用在定义的时候必须进行初始化(引用与某个对象绑定后就不再改变),并且不能够改变。指针在定义的时候不一定要初始化,可为空,并且指向的空间可变,(注:不能有引用的值不能为NULL)某种意义上来说引用可以被认为是不能改变的指针。

3)        级数:有多级指针,但是没有多级引用,引用只有一级。

4)        自增含义:指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)

5)        sizeof大小:sizeof引用得到的是所指向的变量(对象)的大小,而sizeof指针得到的是指针本身的大小。

6)        访问方式:引用访问一个变量是直接访问,而指针访问一个变量是间接访问。

7)        使用指针前最好做类型检查,防止野指针的出现。引用可避免野指针出现;

8)        引用底层是通过指针实现的;

9)        作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引用的实质是传地址,传递的是变量的地址。



4.static的用法和作用?

用来控制变量的存储方式和可见性:需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

static的内部机制:

静态数据成员在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。

static关键字修饰的变量,存储在程序的静态存储区而非栈空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。

1) 静态全局变量:

1.该变量在全局数据区分配内存;

2.未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);

3.静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;

4.静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。

2) 静态局部变量:

1.        该变量在全局数据区分配内存;

2.        静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;

3.        静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;

4.        它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束

3) 静态函数:

静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。其它文件中可以定义相同名字的函数,不会发生冲突。


类中声明static

1) 静态数据成员:

1)        对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。

2)        静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它。

3)        静态数据成员和普通数据成员一样遵从public,protected,private访问规则。

4)        同全局变量相比,使用静态数据成员有两个优势:

1. 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性。

2. 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能。

2) 静态成员函数:

1)        与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。

2)        出现在类体外的函数定义不能指定关键字static。静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数。

3)        非静态成员函数可以任意地访问静态成员函数和静态数据成员。静态成员函数不能访问非静态成员函数和非静态数据成员

4)        由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长。

static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this指针调用的,所以不能为virtual。虚函数的调用关系,this->vptr->ctable->virtual function.

5. const关键字作用?

1) 阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
2) 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
3) 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
4) 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量,类的常对象只能访问类的常成员函数;
5) 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”;
6) const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;
7) 非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;
8) 一个没有明确声明为const的成员函数被看作是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。因此const对象只能调用const成员函数;
9) const类型变量可以通过类型转换符const_cast将const类型转换为非const类型;
10) const类型变量必须定义的时候进行初始化,因此也导致如果类的成员变量有const类型的变量,那么该变量必须在类的初始化列表中进行初始化;
11) 对于函数值传递的情况,因为参数传递是通过复制实参创建一个临时变量传递进函数的,函数内只能改变临时变量,但无法改变实参。则这个时候无论加不加const对实参不会产生任何影响。但是在引用或指针传递函数调用中,因为传进去的是一个引用或指针,这样函数内部可以改变引用或指针所指向的变量,这时const 才是实实在在地保护了实参所指向的变量。因为在编译阶段编译器对调用函数的选择是根据实参进行的,所以,只有引用传递和指针传递可以用是否加const来重载。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。

6. 指针和const的用法?

1

作用

说明

示例

2

定义const常量

const int num = 10;

3

便于进行类型检查

const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。

void f(const int i) { .........}//对传入的参数进行类型检查,不匹配进行提示

4

可以保护被修饰的东西

防止意外的修改,增强程序的健壮性。

void f(const int i) { i=10;//error! }//如果在函数体内修改了i,编译器就会报错

5

为函数重载提供了一个参考

class A

{

void f(int i) {......} //一个函数

void f(int i) const {......} //上一个函数的重载

};

6

可以节省空间,避免不必要的内存分配

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

#define PI 3.14159//常量宏

const doulbe Pi=3.14159;//此时并未将Pi放入ROM

double i=Pi;//此时为Pi分配内存,以后不再分配!

double I=PI; //编译期间进行宏替换,分配内存

double j=Pi;//没有内存分配

double J=PI;//再进行宏替换,又一次分配内存!

7

提高效率

编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,提高效率。

1) const修饰常量:

(1)   const修饰变量,以下两种定义形式在本质上是一样的。它的含义是:const修饰的类型为TYPE的变量value是不可变的。

TYPE const ValueName = value;

const TYPE ValueName = value;

(2)   将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义。

extend const int ValueName = value;

2) 指针使用const

如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量。

如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。

3) 函数中使用const

const修饰函数参数

a) 传递过来的参数在函数内不可以改变(无意义,因为Var本身就是形参)

void function(const int Var);

b) 参数指针所指内容为常量不可变

void function(const char* Var);

c) 参数指针本身为常量不可变(也无意义,因为char* Var也是形参)

void function(char* const Var);

d) 参数为引用,为了增加效率同时防止修改。修饰引用参数时:

void function(const Class& Var); //引用参数在函数内不可以改变

void function(const TYPE& Var); //引用参数在函数内为常量不可变

const引用传递和最普通的函数按值传递的效果是一模一样的,不同的是按值传递会先建立一个类对象的副本, 然后传递过去,而const直接传递地址,所以这种传递比按值传递更有效。只有引用的const传递可以传递一个临时对象,因为临时对象都是const属性,且是不可见的,他短时间存在一个局部域中,所以不能使用指针,只有引用的const传递能够捕捉到这个家伙。

const修饰函数返回值

const修饰函数返回值其实用的并不是很多,它的含义和const修饰普通变量以及指针的含义基本相同。

a)        const int fun1() //这个其实无意义,因为参数返回本身就是赋值。

b)        const int * fun2() //调用时 const int *pValue = fun2();  //我们可以把fun2()看作成一个变量,即指针内容不可变。

c)        int* const fun3() //调用时 int * const pValue = fun2();  //我们可以把fun2()看作成一个变量,即指针本身不可变。

一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例),则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。

4) 类相关const:

a)        const修饰类的成员变量,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。

b)        const修饰类的成员函数,该成员函数不能修改类中任何非const成员函数。

c)        const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。


7. extern关键字用法?

1) extern修饰变量的声明

如果文件a.c需要引用b.c中变量int v,可以在a.c中声明extern int v,然后就可以引用变量v。

2) extern修饰函数的声明

如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int m),那么可以在a.c中声明extern int fun(int m),就可以使用fun()。就像变量的声明一样,extern int fun(int m)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。

3) extern修饰符可用于指示C或者C++函数的调用规范。

如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。


8. 深拷贝与浅拷贝?



1) 浅拷贝(浅复制):只是对指针进行拷贝,拷贝后两个指针指向同一内存空间。换句话说浅拷贝仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。浅拷贝带来问题的本质在于析构函数释放多次堆内存,使用std::shared_ptr,可以完美解决这个问题。

深拷贝(深复制):深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针象。在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
2) 在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

9. C++模板是什么,底层怎么实现的?

1)        编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数。编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

2)        函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。

3)        函数模板和普通函数的区别:函数模板不允许自动类型转换,而普通函数允许自动类型转换。

当函数模板和普通函数在一起时,调用规则如下:

函数模板可以像普通函数一样被重载;

C++编译器优先考虑普通函数;

如果函数模板可以产生一个更好的匹配,那么选择模板;

可以通过空模板实参列表的语法,限定编译器只通过模板匹配。


10. 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,才能做结构类型名。


11. 虚函数可以声明为inline吗?

1) 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。内联是在发生在编译期间,编译器会自主选择内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。

2) 虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。内联函数对于程序中需要频繁使用和调用的小函数非常有用。虚函数要求在运行时进行类型确定,而内联函数要求在编译期完成相关的函数替换。


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

1) 赋值初始化,通过在函数体内进行赋值初始化;列表初始化,在冒号后使用初始化列表进行初始化。
两种方式的主要区别在于:
对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。
列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),
那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。

2) 一个派生类构造函数的执行顺序如下:
虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。
基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。
类类型的成员对象的构造函数(按照初始化顺序)。
派生类自己的构造函数。

3) 必须使用成员初始化的四种情况:
当初始化一个引用成员时;
当初始化一个常量成员时;
当调用一个基类的构造函数,而它拥有一组参数时;
当调用一个成员类的构造函数,而它拥有一组参数时;

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

13. 构造函数为什么不能为虚函数?析构函数为什么要虚函数?

直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
理解:
1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的。问题出来了,假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到相应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
3. 构造函数不须要是虚函数,也不同意是虚函数,由于创建一个对象时我们总是要明白指定对象的类型,虽然我们可能通过实验室的基类的指针或引用去訪问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候假设析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
4. 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,也没有必要成为虚函数。
5. 当一个构造函数被调用时,它做的首要的事情之中的一个是初始化它的VPTR。因此,它仅仅能知道它是“当前”类的,而全然忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(由于类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。并且,仅仅要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但假设接着另一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的还有一个理由。可是,当这一系列构造函数调用正发生时,每一个构造函数都已经设置VPTR指向它自己的VTABLE。假设函数调用使用虚机制,它将仅仅产生通过它自己的VTABLE的调用,而不是最后的VTABLE(全部构造函数被调用后才会有最后的VTABLE)。

因为构造函数本来就是为了明确初始化对象成员才产生的,然而虚函数主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。

14. 构造函数和析构函数可以调用虚函数吗?

在C++中,提倡不在构造函数和析构函数中调用虚函数。虚函数底层实现原理(最好不要在构造和析构函数中调用)虽然可以用,但没有动态绑定的效果,父类构造函数中调用的仍然是父类版本的函数,子类中调用的仍然是子类版本的函数。《effictive c++》第九条,绝不在构造和析构过程中调用virtual,因为构造函数中基类的虚函数不会下降到派生类上,而是直接调用基类的虚函数。绝不在构造和析构函数中调用virtual函数:

a) 如果有继承,构造函数会先调用父类构造函数,而如果构造函数中有虚函数,此时子类还没有构造,所以此时的对象还是父类的,不会触发多态。

b) 析构函数,子类先进行析构,这时如果有virtual函数的话,子类的内容已经被析构了,C++会视其父类,执行父类的virtual函数。

c) 总之,在构造和析构函数中,不要用虚函数。如果必须用,那么分离出一个Init函数和一个close函数,实现相关功能即可。


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

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

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


16. 构造函数析构函数可否抛出异常?

构造函数可以抛出异常,但必须保证构造函数抛出异常前,把系统资源释放掉,防止内存泄漏

1)        构造函数可以抛出异常,但必须保证构造函数抛出异常前,把系统资源释放掉,防止内存泄漏。

a) 构造函数中如果抛出异常,会导致析构函数不能被调用,但对象本身已申请到的内存资源会被系统释放;

b) 因为析构函数不能被调用,所以可能会造成内存泄露或系统资源未被释放。

2) 析构函数不可以抛出异常,析构函数本身就是处理异常的部分。原因如下:

a) 如果析构函数抛出异常,异常点后的程序不会执行,需要把异常完全封装在析构函数内部,使用try catch捕捉;

b) C++机制调用析构函数处理释放资源,若析构函数也抛出异常,那么存在旧的异常未处理,新的异常就来了,会造成程序崩溃。

c) 当在某一个析构函数中可能发生异常时,必须要把这种异常完全封装在析构函数内部,决不能让它抛出函数之外。

C++机制调用析构函数处理释放资源,若析构函数也抛出异常,那么存在旧的异常未处理,新的异常就来了,会造成程序崩溃。

17. 类如何实现只能静态分配和只能动态分配?

1) 静态分配:把new、delete运算符重载为private属性。动态分配:把构造、析构函数设为protected属性,再用子类来动态创建
2) 建立类的对象有两种方式:
静态建立,静态建立一个类对象,就是由编译器为对象在栈空间中分配内存;
动态建立,A *p = new A();动态建立一个类对象,就是使用new运算符为对象在堆空间中分配内存。这个过程分为两步,第一步执行operator new()函数,在堆中搜索一块内存并进行分配;第二步调用类构造函数构造对象。
3) 只有使用new运算符,对象才会被建立在堆上,因此只要限制new运算符就可以实现类对象只能建立在栈上。可以将new运算符设为私有。

18. 什么情况会自动生成默认构造函数?

1) 带有默认构造函数的类成员对象,如果一个类没有任何构造函数,但它含有一个成员对象,而后者有默认构造函数,那么编译器就为该类合成出一个默认构造函数。不过这个合成操作只有在构造函数真正被需要的时候才会发生;如果一个类A含有多个成员类对象的话,那么类A的每一个构造函数必须调用每一个成员对象的默认构造函数而且必须按照类对象在类A中的声明顺序进行;
2) 带有默认构造函数的基类,如果一个没有任务构造函数的派生类派生自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;
3) 带有一个虚函数的类;
4) 带有一个虚基类的类;
5) 合成的默认构造函数中,只有基类子对象和成员类对象会被初始化。所有其他的非静态数据成员都不会被初始化。

19.为什么友元函数必须在类内部声明?

因为编译器必须能够读取这个结构的声明以理解这个数据类型的行为等方面的所有规则。
有一条规则在任何关系中都很重要,那就是谁可以访问我的私有部分。

20. 介绍一下C++里面的多态?

1)静态多态(重载,模板),静态多态意味着接口重用
是在编译的时候,就确定调用函数的类型。
(2)动态多态(覆盖-也称重写,虚函数实现)
在运行的时候,才确定调用的是哪个函数,动态绑定。运行基类指针指向派生类的对象,并调用派生类的函数。
虚函数实现原理:虚函数表和虚函数指针。
纯虚函数定义: virtual int fun() = 0;
函数的运行版本由实参决定,在运行时选择函数的版本,所以动态绑定又称为运行时绑定。
当编译器遇到一个模板定义时,它并不生成代码。只有当实例化出模板的一个特定版本时,编译器才会生成代码。

未完待续~

需资料分享,可私聊哈。



#C/C++##学习路径#
全部评论
m
点赞 回复 分享
发布于 2021-03-07 19:27
感谢参与【创作者计划2期·技术干货场】!欢迎更多牛油来写干货,瓜分总计20000元奖励!!技术干货场活动链接:https://www.nowcoder.com/link/czz2jsghtlq(参与奖马克杯将于每周五结算,敬请期待~)
点赞 回复 分享
发布于 2021-03-08 16:14
m
点赞 回复 分享
发布于 2021-03-11 17:57
厉害😂过来学习一波
点赞 回复 分享
发布于 2021-11-27 10:21
m
点赞 回复 分享
发布于 2021-12-21 17:36
关于5点const那里:const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;这句好像不对,const成员函数也不可以访问const对象的非const数据成员吧,因为const对象就是不可以访问非const的数据成员呀
点赞 回复 分享
发布于 2023-04-23 23:28 美国
m
点赞 回复 分享
发布于 2023-07-25 21:06 山西
mmmmm
点赞 回复 分享
发布于 2023-08-01 10:03 广东

相关推荐

评论
32
179
分享
牛客网
牛客企业服务