C++后端工程师高频面试题(含解析)

1、什么是虚函数?

为什么需要虚函数? 虚函数是在基类中声明的带有virtual关键字的成员函数。它允许在派生类中重写该函数,实现多态性。通过使用虚函数,可以根据对象的实际类型来调用相应的方法。

2、什么是纯虚函数和抽象类?

纯虚函数是在基类中声明但没有实现的虚函数,通过在函数声明末尾添加 “= 0” 来指定。抽象类至少包含一个纯虚函数,因此不能被实例化,只能作为其他派生类的基类。

3、C++中动态内存分配和释放的方式有哪些?

C++提供了new和delete操作符用于动态内存分配和释放。也可以使用malloc()和free()来进行内存管理,但需要注意与对应的delete和free相匹配使用。

4、解释一下浅拷贝和深拷贝。

浅拷贝只复制指针地址,多个对象共享同一块内存空间。深拷贝会为每个对象分配独立的内存空间,并复制内容到新地址。

5、什么是析构函数?

为什么需要析构函数? 析构函数是在对象销毁时自动调用的函数,用于释放对象占用的资源。析构函数的名称与类名相同,前面加上波浪号(~)。

6、C++中常见的内存泄漏问题有哪些?

如何避免内存泄漏? 常见的内存泄漏问题包括未释放动态分配的内存、循环引用等。为了避免内存泄漏,需要正确使用new/delete或智能指针来管理动态内存,并及时释放不再使用的资源。什么是RAII(Resource Acquisition Is Initialization)机制? RAII是一种资源获取即初始化的技术,在构造函数中获取资源,在析构函数中释放资源,确保在对象生命周期结束时资源被正确释放。解释一下const关键字在C++中的作用。 const关键字表示一个常量,可以防止变量被修改。它可以应用于变量、函数参数、成员函数以及返回值类型。

7、C++中如何处理异常?

C++通过try-catch块来处理异常。try块包含可能抛出异常的代码,catch块捕获并处理异常。还可以使用throw语句显式地抛出异常。什么是模板类和模板函数? 模板类和模板函数都是通用化编程的方式。模板类可以定义通用数据结构或算法,模板函数可以定义通用的函数。解释一下C++中的多态性。 多态性是面向对象编程的特性之一,它允许以不同的方式处理不同类型的对象。通过虚函数和继承关系,可以在运行时确定调用哪个实现。

8、C++中什么是命名空间?

为什么使用命名空间? 命名空间是一种将全局作用域划分为更小、更可管理的区域的机制。它可以避免名称冲突,并提供代码组织和模块化。

9、什么是引用和指针?它们有何区别?

引用是一个已存在变量的别名,使用&符号声明。指针是一个存储变量地址的变量,使用*符号声明。主要区别在于指针可以为空,而引用必须引用一个已经存在的对象。

10、解释一下const修饰成员函数。

const修饰成员函数表示该成员函数不会修改对象状态,并且不能调用其他非const成员函数(除非通过const_cast进行转换)。

11、什么是重载和重写?

重载(Overload)指在同一个作用域内根据不同参数列表来定义多个具有相同名称但功能不同的函数。重写(Override)指在派生类中重新定义基类的虚函数。

12、C++中如何实现多继承?

C++支持多继承,通过在派生类的声明中使用逗号分隔多个基类。

13、解释一下C++中的静态成员和静态函数。

静态成员是属于整个类而不是对象实例的成员。静态函数是只能访问静态成员,并且没有this指针。

14、什么是移动语义(Move Semantics)?它有什么作用?

移动语义允许将资源所有权从一个对象转移到另一个对象,而不需要进行深拷贝。它可以提高性能并减少内存开销,尤其对于大型对象或容器来说效果明显。

15、解释一下C++11中的智能指针。

C++11引入了shared_ptr、unique_ptr和weak_ptr等智能指针类型,用于管理动态分配的内存资源,自动进行内存释放。

16、什么是RAII(Resource Acquisition Is Initialization)机制?

RAII是一种资源获取即初始化的技术,在构造函数中获取资源,在析构函数中释放资源,确保在对象生命周期结束时资源被正确释放。

17、解释一下const关键字在C++中的作用。

const关键字表示一个常量,可以防止变量被修改。它可以应用于变量、函数参数、成员函数以及返回值类型。

18、什么是模板类和模板函数?

模板类和模板函数都是通用化编程的方式。模板类可以定义通用数据结构或算法,模板函数可以定义通用的函数。

19、解释一下C++中的多态性。

多态性是面向对象编程的特性之一,它允许以不同的方式处理不同类型的对象。通过虚函数和继承关系,可以在运行时确定调用哪个实现。

本文福利, 免费领取C++1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件等等面试题)↓↓↓↓有需要的可以进企鹅裙927239107领取哦~↓↓

20、什么是模板元编程(Template Metaprogramming)?

模板元编程是利用C++模板机制在编译期间进行计算和代码生成的技术。它可以实现编译时常量计算、条件编译等功能。

21、C++中如何处理线程和并发操作?

C++标准库提供了thread、mutex、condition_variable等类来支持多线程编程和并发操作。

22、解释一下C++中的虚拟继承(Virtual Inheritance)。

虚拟继承用于解决多重继承中的菱形继承问题,通过使用关键字virtual来声明虚拟基类,确保只有一个实例被派生类共享。

23、什么是函数对象(Functor)?

函数对象是一种行为类似函数的对象,可以像函数一样调用。它通常是一个类,重载了函数调用运算符()。

24、解释一下C++中的类型转换。

C++提供了static_cast、dynamic_cast、reinterpret_cast和const_cast等不同的类型转换方式。每种类型转换都有其特定的用途和限制。

25、什么是内联函数(Inline Function)?为什么使用内联函数?

内联函数是在编译时将函数体插入到调用处的函数。它可以减少函数调用开销,并提高执行效率。通常适用于简短的代码片段。

26、C++中如何处理文件输入输出?

C++标准库提供了iostream头文件,包含了对文件输入输出的支持,可以使用ifstream、ofstream和fstream等类进行文件读写操作。

27、解释一下C++中的命令行参数传递。

main()函数可以接受命令行参数,通过argc和argv两个参数来传递命令行参数的数量和内容。

28、什么是lambda表达式?

lambda表达式是一种匿名函数的形式,可以在需要函数对象的地方使用。它具有捕获列表、参数列表和函数体,可以方便地定义简短的可调用对象。

29、C++中如何进行异常安全处理?

异常安全处理是确保程序在发生异常时不会泄漏资源或导致不一致状态的技术。可以使用RAII、异常规范、try-catch等方式来实现异常安全性。

30、解释一下C++中的重定向操作符(<< 和 >>)。

重定向操作符 << 和 >> 是用于输入输出流的操作符,可以将数据从流中提取或插入到变量中。它们也可以重载以实现自定义类型的输入输出格式。

31、使用const而不是#define来定义常量?

  • ①const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而后者仅仅进行字符替换,没有类型安全检查,并且在字符替换过程中会产生一些意料不到的错误。
  • ②const方法可以很方便地用于复合类型。
    如const int a[3]={1,2,3};
  • ③const标识符遵循变量的作用域原则,可以创建作用域为全局、名称空间、数据块的常量。

32、C++左值和右值的区别?

  • 左值:lvalue(locator value)代表一个在内存中占有确定位置的对象(换句话说就是有一个地址)。
  • 右值:rvalue通过排他性来定义,每个表达式不是lvalue就是rvalue。因此从上面的lvalue的定义,rvalue是在不在内存中占有确定位置的表达式。

33、全局变量?

特点:

  • ① 作用域:全局可见。
    全局变量(外部变量)是在函数外部定义的,它的作用域为从变量的定义处开始,到本程序文件的末尾。
    注:通常把超出一个函数的作用域称为全局作用域,其他几种(如块作用域)不超出一个函数的作用域称为局部作用域。
  • ② 存储空间:静态存储区
    系统会在编译时将全局变量分配在静态存储区,在程序执行期间,对应的存储空间不会释放,一直到程序结束才会释放。
    注:一个程序在内存中占用的存储空间可以分为3个部分:程序区(存放可执行程序的代码)、静态存储区(存放静态变量)、动态存储区(存放动态变量)。
  • ③ 优先度:全局变量优先度低于局部变量
    当全局变量和局部变量重名时,会屏蔽全局变量,局部优先。

优点:使用全局变量程序运行时速度会快一点,因为内存不需要再分配。
缺点:使用全局变量会占用更多的内存,因为其生命期长。

34、全局变量作用域的扩展和限制:

  • ① 扩展:使用extern关键字可以对全局变量的作用域进行扩展。
    前面提到,全局变量的作用域为从变量的定义处开始,到本程序文件的末尾。若想在本文件全局变量定义之前引用该全局变量,可以在引用之前用extern关键字对该变量进行说明,有了此说明,就可以从说明之处起,合法地引用该变量。
    若想在一个文件(设为a.cpp)中引用另一个文件(设为b.cpp)中已定义的全局变量,可以在a.cpp中extern关键字对该全局变量进行说明,在编译和连接时,系统就会知道该全局变量已经在其他文件(b.cpp)中定义过了。
    注:在编译时遇到extern,系统会现在本文件中查找全局变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时在其他文件中查找全局变量的定义,如果找到,就将作用域扩展到本文件;如果还找不到,按出错处理。
  • ② 限制:使用static关键字可以限制全局变量的作用域。(又称之为隐藏)
    全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern对全局变量进行声明,就可以使用全局变量。
    如果希望全局变量仅限本文件引用,而不能被其他文件引用,可以在定义全局变量时在前面加一个static关键字。

35、什么时候用static?

需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

36、为什么要引入static?

函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题:如果想将函数中此变量的值保存至下一次调用时,如何实现?最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。

37、为什么要使用智能指针?

动态内存的使用中很容易出现问题。比如,申请的空间忘记释放,造成内存泄漏;或者在后面还会使用到该指针的情况下释放了内存,在这种情况下就引用了非法内存的指针。
用智能指针可以很大程度上的避免这些问题,因为智能指针就是一个类(而且还是像vector这样的模板类,当我们创建一个智能指针时,还必须提供额外的信息——指针可以指向的类型),当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

38、堆(heap)和栈(stack)的区别?

(1) 申请方式和回收方式不同
栈是系统自动分配空间的,例如我们定义一个 char a;系统会自动在栈上为其开辟空间。而堆则是程序员根据需要自己申请的空间,例如malloc(10);开辟十个字节的空间。由于栈上的空间是自动分配自动回收的,所以栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。而堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。

(2) 申请后系统的响应
栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:堆上申请空间的效率比栈要低得多。
首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

39、程序的内存模型分为那几个区域?

4个区域:静态存储区、堆区、栈区、程序区
(1)静态存储区:存放全局变量、静态变量、常量。
(2)堆区:由程序员根据需要自己申请空间,需要自己回收这些空间,如果没有回收,程序结束时操作系统会回收这些空间。也用来保存类和对象。
(3)栈区:存放函数的参数、返回值、局部变量等。
(4)代码区:存放代码。

也有人说C++五个区域:静态存储区、堆区、栈区、程序区、自由存储区。
其他四个区域功能依旧,C++里增加了new关键字,new申请的内存是在自由存储区上。我并没有搞懂自由存储区和堆区的区别。

40、知道如果用free去清理new出来的内存会产生什么问题吗?

new实际过程中做了两步操作,第一步是分配内存空间,第二步是调用类的构造函数;delete也同样是两步,第一步是调用类的析构函数,第二步才是释放内存;而malloc()和free()仅仅是分配内存与释放内存操作。

如果通过new分配的内存,再用free去释放,就会少一步调用析构函数的过程。同时,在构造函数里面申请的内存因为没有调用析构函数,所以该内存并没有释放,所以如果再输出该内存的值,那么应该还是原来设置的值。

41、深拷贝和浅拷贝?

(1) 浅拷贝
浅拷贝只是增加了一个指针指向已存在的内存地址,实际内存中并没有重新开一块内存复制原来地址内的内容。比如变量的引用就是浅拷贝,这个变量只是多了一个别名,内存中并没有额外用一块内存重新保存这个变量,我改变引用的值,原来变量的值也会跟着改变。

(2)深拷贝
深拷贝是申请了一个新的内存保存复制的内容,而且增加了一个指针并且使这个指针指向这块新的内存,相当于有两块内存保存了一样的内容。我改变这块新内存中的内容,原来那块内存中的内容不会改变。

42、C++多态实现的机制?(重载和重写的区别?)

多态就是多种形态,C++的多态分为静态多态与动态多态。
静态多态就是重载,编译器根据函数实参的类型判断出要调用哪个函数。比如函数重载和函数模板。
动态多态依靠的是虚函数表和动态绑定机制,因为是在运行时根据对象的类型在虚函数表中寻找调用函数的地址来调用相应的函数,所以称为动态多态。

(1)什么是多态?
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的类型来调用相应的函数。如果对象的类型是派生类,就调用派生类的函数;如果对象是基类,就调用基类的函数。

(2) 虚函数表?
声明了虚函数的类会隐式创建一个虚指针指向虚函数表,而虚函数表保存了虚函数的地址(虚函数表中只有虚函数,非虚函数在另一个表中),当我们调用虚函数时,实际上是通过虚指针去查找虚函数表中对应虚函数的地址。

43、为什么list不能使标准库算法sort()?

list的内存空间不是连续的,标准库算法中的操作需要随机访问迭代器。因此forward_list也不可以。

44、解决哈希冲突的方法有哪些?

(1)开放地址法
当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。
(2)再哈希法
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。
(3) 建立一个公共溢出区
除了原本的存储空间外,另外开辟一个存储空间用以存储发生冲突的记录。

45、容器适配器?

C++提供了三种容器适配器(container adapter):栈stack、队列queue、优先队列priority_queue。stack和queue基于deque实现,priority_queue基于vector实现。
假设我需要一个栈结构,可以用deque来模拟,只在一端进行元素插入和弹出,另一端不动,但我不能防止别人在deque的另一端进行操作,因此deque并不能严格地满足我的要求。我对它进行封装,作一些限制,使它留出的接口只能在一端进行插入和删除,这样就实现了stack。实际上stack也是使用的deque,只是对deque进行了封装改变了对外的接口而已。因此,stack、queue、priority_queue这样的类一般称为容器适配器,它们只是基本容器类型(vector,dequeue,list)的适配。

46、定位内存泄露

(1)在windows平台下通过CRT中的库函数进行检测;
(2)在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置

47、如何避免死锁

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求和保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不可抢占条件:进程已获得的资源,在末使用完之前,不能强行剥夺,只能在进程使用完时由自己释放。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

48、STL各容器的实现原理

1)Vector顺序容器,是一个动态数组,支持随机插入、删除、查找等操作,在内存中是一块连续的空间。在原有空间不够情况下自动分配空间,增加为原来的两倍。vector随机存取效率高,但是在vector插入元素,需要移动的数目多,效率低下。

注:vector动态增加大小时是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对vector空间重新配置,指向原vector的所有迭代器就都失效了。

2)Map关联容器,以键值对的形式进行存储,方便进行查找。关键词起到索引的作用,值则表示与索引相关联的数据。红黑树的结构实现,插入删除等操作都在O(logn)时间内完成。

3) Set是关联容器,set每个元素只包含一个关键字。set支持高效的关键字检查是否在set中。set也是以红黑树的结构实现,支持高效插入、删除等操作。

49、线程之间的通信方式

锁机制 包括互斥锁/量(mutex)、读写锁(reader-writer lock)、自旋锁(spin lock)、条件变量(condition)

  1. 互斥锁/量(mutex):提供了以排他方式防止数据结构被并发修改的方法。
  2. 读写锁(reader-writer lock):允许多个线程同时读共享数据,而对写操作是互斥的。
  3. 自旋锁(spin lock)与互斥锁类似,都是为了保护共享资源。互斥锁是当资源被占用,申请者进入睡眠状态;而自旋锁则循环检测保持者是否已经释放锁。
  4. 条件变量(condition):可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

信号量机制(Semaphore)

  1. 无名线程信号量
  2. 命名线程信号量 信号机制(Signal):类似进程间的信号处理 屏障(barrier):屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制

50、红黑树比AVL的优势,为何用红黑树

红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树, avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多

本文福利, 免费领取C++1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件等等面试题)↓↓↓↓有需要的下方领取哦~↓↓

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/134930559