【面经】后端开发岗面试题目汇总

后端开发岗(C++、Java)面试题目汇总

  • README
  • 一、C++相关
    • 1. struct和class的区别
    • 2. 指针和引用的区别
    • 3. 常量指针、指针常量、常量引用、引用
    • 4. new和malloc的区别
    • 5. 如何检测一个链表是否有环
    • 6. C++虚函数实现
  • 二、Java相关
    • 1. Java GC(为什么new完之后不用delete)
    • 2. equals() 和 == 的区别
  • 二、计算机网络相关
  • 三、操作系统相关
    • 1. 线程和进程
  • 四、数据库及SQL相关
    • 1. 各种SQL语句
  • 五、数据结构与算法相关

README

本人经历了四五场鹅厂、阿里、字节等后端开发岗的面试,奈何技术不够硬。为了广大友军能够顺利找到工作,特此总结了本篇面经。
首先,奉劝广大友军一定要有自己擅长的领域,本人啥都会一点但是啥都不精,往往经不起面试官的重重追问,所以技术不需要广但是必须需要专,而且你们有自己擅长的领域的话可以把面试官往自己擅长的领域带!
其次,面试其实就像是一场毕业考试,C++、Java、Python、Go等编程语言就不说了,像是计算机网络、操作系统、数据库及SQL、数据结构、算法等课程的知识在面试中都会涉及。所以大家一定要提前准备,在例如Leetcode等网站上面多刷题!但是其实有一个小技巧,大家在复习的时候一定要结合自己的岗位针对性地复习,例如本人面的C++后端开发/C++研发工程师,考察的比较多的就是C++特性及底层实现和计算机网络,所以还是有一定复习的范围的。
还有,记住不要惧怕面试官!大胆点!
接下来就是具体的一些题目汇总,其答案都是本人为了容易记住而简化的版本,要详细理解的话大家可以自行查阅书籍或互联网资料。
(本文将不定期更新,欢迎大家收藏)

一、C++相关

1. struct和class的区别

  • 在C++中struct和class没有太大的区别,C++中的struct是为了保证对C语言的兼容性,并且对struct做了很多扩展,例如可以包含成员函数、静态成员、访问控制可以为public/private/protected、可以继承等等;
  • C++中struct和class的区别在于struct的默认访问控制是publicclass的默认访问控制是private
  • 一般来说struct用于数据结构集合而class用语面向对象编程;

2. 指针和引用的区别

  • 引用不可以为空,但指针可以为空。所以定义一个引用的时候,必须初始化,而使用指针之前必须做判空操作;
  • 引用不可以改变指向,对一个对象”至死不渝”,但是指针可以改变指向,而指向其它对象;
  • 引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节

3. 常量指针、指针常量、常量引用、引用

  • 引用和常量引用:“int &a = 2020”是错误的,而"const int &a = 2020"是正确的;
  • 为什么没有引用常量:因为引用本来就不能改变指向;
  • 常量指针和指针常量:常量指针指向的是一个常量,即指向的对象不能通过这个指针来修改;而指针常量所指向的值不可以改变,即指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化;

4. new和malloc的区别

  • C语言提供了malloc和free两个系统函数,完成对堆内存的申请和释放;而C++则提供了两个关键字new和delete,效率高于malloc和free;
  • 属性区别:new和delete是C++关键字,需要编译器支持;malloc和free是库函数,需要头文件支持;
  • 参数:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸
  • 返回类型:new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型;
  • 自定义类型:new会先调用operator new()函数,申请足够的内存(通常底层使用malloc实现),然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针,delete先调用析构函数,然后调用operator delete()函数释放内存(通常底层使用free实现);malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作
  • 重载:C++允许重载new/delete操作符,malloc/free不允许重载;
  • 内存泄漏问题:内存泄漏对于new和malloc都能检测出来,而new可以指明是哪个文件的哪一行malloc却不可以
  • 内存区域:new做两件事:分配内存和调用类的构造函数、delete是:调用类的析构函数和释放内存;而malloc和free只是分配和释放内存;new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中;

5. 如何检测一个链表是否有环

  • 方法一:设置一个快指针fast,一个慢指针slow,二者初始都指向链表头,fast一次走两步,slow一次走一步,两个指针同时向前移动,每移动一次,两个指针都要进行比较,如果快指针等于慢指针,则证明这是个有环的单链表,否则如果fast先行到达链表尾部或为NULL,则证明这是个不带环的单链表;
  • 方法二:可以把已经遍历过的节点用一个HashSet存起来,然后每遍历一个新节点的时候再去和HashSet中的元素做匹配,如果HashSet中已存在该节点,那么单链表有环;

6. C++虚函数实现

  • 编译器见到这种继承层次结构后,知道Base定义了虚函数,并且在Derived类中覆盖了这些函数。在这种情况下,编译器将为实现了虚函数的基类和覆盖了虚函数的派生类分别创建一个虚函数表(Virtual Function Table,VFT)。也就是说Base和Derived类都将有自己的虚函数表。实例化这些类的对象时,将创建一个隐藏的指针VFT*,它指向相应的VFT。可将VFT视为一个包含函数指针的静态数组,其中每个指针都指向相应的虚函数。每个虚函数表都由函数指针组成,其中每个指针都指向相应虚函数的实现。在类Derived的虚函数表中,除一个函数指针外,其他所有的函数指针都指向本地的虚函数实现。Derived没有覆盖Base::Func2,因此相应的虚函数指针指向Base类的Func2的实现。这就意味着,当执行代码时,编译器将查找Derived类的VFT,确保调用Base::Func2的实现。

二、Java相关

1. Java GC(为什么new完之后不用delete)

  • Java存在垃圾回收机制,是先不断的分配内存,只要能分配就分配,如果不能分配了,再查找一下哪些内存已经不被程序使用了,就回收它,那么java可以在程序运行的过程中就回收内存;
  • 如何判断GC的对象:(1)引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题;(2)可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的不可达对象。

2. equals() 和 == 的区别

  • 基本数据类型,也称原始数据类型。byte, short, char, int, long, float, double, boolean 他们之间的比较,应用双等号(==),比较的是他们的值;
  • 复合数据类型(类),当他们用 == 进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String, Integer, Date等。在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。 对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号 == 进行比较的,所以比较后的结果跟双等号 == 的结果相同。而到了我们自己编写的类,对象相等的标准由我们确立,于是就不可避免的要覆写继承而来的public boolean equals(Object obj),就是比较对象的属性(数据成员)。

二、计算机网络相关

三、操作系统相关

1. 线程和进程

  • 线程的定义:线程是在进程内部的执行流,也被称为轻量进程(LightWeight Process,LWP),是CPU调度的基本单元同一个进程内的线程之间共享所有资源每个进程内至少有一个线程
  • 为什么引入线程:由于进程是资源的拥有者,所以在创建、撤销、切换操作中需要较大的时空开销,限制了并发程度的进一步提高。为减少进程切换的开销,把进程作为资源分配单位和调度单位这两个属性分开处理,即进程还是作为资源分配的基本单位,但是不作为调度的基本单位(很少调度或切换),把调度执行与切换的责任交给“线程”。这样做可以提高系统的并发度;
  • 进程线程的基础区别:
    a. 进程是资源分配的最小单位线程是程序执行的最小单位(资源调度的最小单位)
    b. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵;而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多;
    c. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行;
    d. 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间;
  • 进程线程的高级区别:
    a. 因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这对于多进程来说十分“奢侈”,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉;
    b. 体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便;
    c. 体现在CPU系统上面,线程使得CPU系统更加有效,因为操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;
    d. 体现在程序结构上,举一个简明易懂的列子:当我们使用进程的时候,我们不自主的使用if else嵌套来判断pid,使得程序结构繁琐,但是当我们使用线程的时候,基本上可以甩掉它,当然程序内部执行功能单元需要使用的时候还是要使用,所以线程对程序结构的改善有很大帮助;

四、数据库及SQL相关

1. 各种SQL语句

  • 数据查询语句 DQL(select):从数据库(表)中查询字段的值;
  • 数据操纵语句 DML(insert、update、delete、merge):对数据库中的数据进行增、删、改等操作,其中merge into语句可高效率地执行批量更新;
  • 数据控制语句 DCL(grant、revoke):设置或更改数据库用户或角色权限;
  • 事务控制语言 TCL(commit、rollback、savepoint)
  • 数据定义语言 DDL(create、alter、drop、truncate、rename、comment):用在定义或改变表的结构、数据类型等,即对表进行操作,不涉及记录的修改,应注意DDL操作是隐形提交的,即不能ROLLBACK;

五、数据结构与算法相关

你可能感兴趣的:(【面经】后端开发岗面试题目汇总)