字节客户端也疯狂拷打基础!

2023年 8月 10日 120.3k 0

大家好,我是小林。

关注我的同学,有很多都是学C++的同学,针对互联网后端岗位的话,C++可能没有太多优势,因为很少项目是用 C++ 做后端业务类型的开发了,主流的还是 java 和 go 后端。

但是,很多互联网客户端岗位会喜欢 C++ 同学,因为学 C++的同学,通常计算机基础都还不错,很多人可能不太知道客户端岗位具体会问什么,其实主要也是围绕 C++、网络、操作系统、算法,这四大块问了,像数据库、消息队列后端组件这些就不会问了。

这次就分享一位 C++同学,面字节客户端岗位的面经,都是围绕C++、网络、操作系统、算法这四大块内容去问了。

C++

C++中的内存分区有哪些?

在C++中,内存主要分为以下五个区域:

图片图片

  • 栈区(Stack):由编译器自动分配释放,存放函数的参数值,局部变量等。其操作方式类似于数据结构中的栈。
  • 堆区(Heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意,与数据结构中的堆是两回事,分配方式倒是类似于链表。
  • 全局区(静态区)(Static):全局变量和静态变量被分配到同一块内存中。在C++中,全局区还包含了常量区,字符串常量和其他常量也是存储在此。
  • 常量区:是全局区的一部分,存放常量,不允许修改。
  • 代码区(Text):存放函数体的二进制代码。

介绍一下内存对齐

内存对齐就是就是将数据存放在内存的某个位置,使得CPU可以更快地访问到这个数据,以空间换时间的方式来提高 cpu 访问数据的性能。

在C++中,内存对齐主要涉及到两个概念:对齐边界和填充字节。

  • 对齐边界:一般情况下,编译器会自动地将数据存放在它的自然边界上。例如,int类型的数据,它的大小为4字节,编译器会将其存放在4的倍数的地址上。这就是所谓的对齐边界。
  • 填充字节:为了满足对齐边界的要求,编译器有时候需要在数据之间填充一些字节。这些字节没有实际的意义,只是为了满足内存对齐的要求。

为什么要字节对齐?

  • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

vector中push_back和emplace_back的区别?

  • push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);
  • 而emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

C++中的多态怎么实现的?

C++中的多态主要通过虚函数和继承来实现。多态分为两种:编译时多态和运行时多态。

  • 编译时多态:也称为静态多态或早绑定。这种多态是通过函数重载和模板来实现的。
  • 运行时多态:也称为动态多态或晚绑定。这种多态是通过虚函数和继承来实现的。当基类的指针或引用指向派生类对象时,调用的虚函数将是派生类的版本,这就实现了运行时多态。

什么是纯虚函数?有哪些应用场景

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在C++中,纯虚函数的声明形式如下:

virtual void function() = 0;

其中,= 0就表示这是一个纯虚函数。

含有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为接口使用。派生类必须实现所有的纯虚函数,否则该派生类也会变成抽象类。

纯虚函数的应用场景主要包括:

  • 设计模式:例如在模板方法模式中,基类定义一个算法的骨架,而将一些步骤延迟到子类中。这些需要在子类中实现的步骤就可以声明为纯虚函数。
  • 接口定义:可以创建一个只包含纯虚函数的抽象类作为接口。所有实现该接口的类都必须提供这些函数的实现。

为什么一般将析构函数设置为虚函数?

析构函数被设为虚函数主要是为了解决基类指针指向派生类对象时的资源释放问题。

如果我们有一个基类指针,它实际上指向一个派生类对象,当我们删除这个基类指针时,如果析构函数不是虚函数,那么就只会调用基类的析构函数,而不会调用派生类的析构函数。这可能会导致派生类对象的一些资源没有被正确释放,从而引发内存泄漏等问题。

如果我们将析构函数设置为虚函数,那么在删除基类指针时,会首先调用派生类的析构函数,然后再调用基类的析构函数,从而确保所有的资源都能被正确释放。

什么是内联函数?

在C++中,使用关键字"inline"可以声明一个内联函数。声明为内联函数的函数会在编译时被视为候选项,编译器会尝试将其展开,将函数体直接插入到调用点处。这样可以避免函数调用的开销,减少了函数调用的栈帧等额外开销,从而提高程序的执行效率。

内联函数有什么缺点?

内联函数的缺点主要有以下几点:

  • 代码膨胀:内联函数会在每个调用它的地方进行代码替换,这可能导致代码膨胀。如果内联函数体非常大或者被频繁调用,会增加可执行文件的大小,可能导致缓存不命中,影响性能。
  • 编译时间增加:内联函数需要在每个调用点进行代码替换,这会增加编译时间。特别是当内联函数被广泛使用时,编译时间可能会显著增加。
  • 可读性降低:内联函数会将函数体嵌入到调用点,可能导致代码的可读性降低。函数体被分散在多个地方,可能会使代码难以理解和维护。

网络

http状态码有哪些?

五大类 HTTP 状态码

图片图片

  • 1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。
  • 2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
  • 3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
  • 4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。
  • 5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。

http1.1、2.0版本的区别?

HTTP/1.1和HTTP/2.0是两个不同版本的HTTP协议,它们之间有以下几个主要区别:

  • 多路复用:HTTP/1.1中,每个请求都需要建立一个独立的连接,而HTTP/2.0引入了多路复用技术,允许在同一个连接上同时发送多个请求和接收多个响应,提高了并发性能。
  • 二进制分帧:HTTP/2.0使用二进制分帧机制,将请求和响应数据分割为更小的帧,每个帧都有自己的标识和优先级,可以独立传输和处理,提高了数据传输的灵活性和效率。
  • 首部压缩:HTTP/2.0使用了首部压缩算法,减少了请求和响应的首部大小,节省了带宽和传输时间。
  • 服务器推送:HTTP/2.0支持服务器推送,服务器可以在客户端请求之前主动将相关资源推送给客户端,减少了额外的请求延迟。

在浏览器输入URL之后,具体流程是什么?

具体的流程如下:

  • URL解析:浏览器首先解析输入的URL,提取出协议、主机名、端口号、路径等信息。
  • DNS解析:浏览器将主机名转换为对应的IP地址,通过DNS解析来完成这一步骤。
  • 建立TCP连接:浏览器与服务器之间建立TCP连接,通过三次握手建立可靠的连接。
  • 发送HTTP请求:浏览器构建HTTP请求报文,包括请求方法(GET、POST等)、请求头部、请求体等信息,并将其发送给服务器。
  • 服务器处理请求:服务器接收到请求后,根据请求的路径、参数等进行处理,并生成对应的HTTP响应。
  • 接收HTTP响应:浏览器接收到服务器返回的HTTP响应报文,包括响应状态码、响应头部、响应体等信息。
  • 渲染页面:浏览器根据接收到的响应数据,解析HTML、CSS、JavaScript等资源,并进行页面的渲染,展示给用户。
  • 关闭TCP连接:页面渲染完成后,浏览器与服务器之间的TCP连接会被关闭,释放网络资源。

tcp 是怎么实现可靠传输的?

  • 序列号与确认应答:TCP将每个发送的数据包进行编号(序列号),接收方通过发送确认应答(ACK)来告知发送方已成功接收到数据。如果发送方在一定时间内未收到确认应答,会进行超时重传。
  • 数据校验:TCP使用校验和来验证数据的完整性。接收方会计算接收到的数据的校验和,并与发送方发送的校验和进行比较,以检测数据是否在传输过程中发生了错误。
  • 窗口控制:TCP使用滑动窗口机制来控制发送方和接收方之间的数据流量。发送方根据接收方的处理能力和网络状况来调整发送的数据量,接收方则通过窗口大小来告知发送方可以接收的数据量。
  • 重传机制:如果发送方未收到确认应答或接收方检测到数据错误,TCP会进行重传。发送方会根据超时时间或接收方的冗余确认来触发重传,以确保数据的可靠传输。
  • 拥塞控制:TCP使用拥塞控制算法来避免网络拥塞。通过动态调整发送速率和窗口大小,TCP可以根据网络的拥塞程度来进行适当的调整,以提高网络的利用率和稳定性。

IP数据报的报头有哪些字段?

图片图片

IP数据报的报头包含以下字段:

  • 版本(Version):指定IP协议的版本,通常为IPv4或IPv6。
  • 首部长度(Header Length):指定IP报头的长度,以32位字(4字节)为单位。
  • 服务类型(Type of Service):用于指定数据报的服务质量要求,如优先级、延迟、吞吐量等。
  • 总长度(Total Length):指定整个IP数据报的长度,包括报头和数据部分。
  • 标识(Identification):用于唯一标识一个IP数据报,通常由发送方设置,接收方用于重组分片。
  • 标志(Flags):包含3个标志位,分别是DF(Don't Fragment,不分片)、MF(More Fragments,更多分片)、和保留位。
  • 分片偏移(Fragment Offset):用于指示当前分片相对于原始数据报的偏移量,以8字节为单位。
  • 生存时间(Time to Live):指定数据报在网络中可以经过的最大路由器跳数,每经过一个路由器,该值减1,为0时数据报被丢弃。
  • 协议(Protocol):指定IP数据报中承载的上层协议,如TCP、UDP、ICMP等。
  • 头部校验和(Header Checksum):用于检验IP报头的完整性,接收方使用该字段来验证报头是否正确。
  • 源IP地址(Source IP Address):指定发送方的IP地址。
  • 目标IP地址(Destination IP Address):指定接收方的IP地址。
  • IP 报文的TTL是什么意思?

    指定数据报在网络中可以经过的最大路由器跳数。每当数据报经过一个路由器时,该字段的值会减少1。当TTL的值为0时,路由器将丢弃该数据报并发送ICMP的时间超过消息给源主机。

    TTL的主要目的是防止数据报在网络中无限循环,避免由于路由环路或其他问题导致的数据报无法正常到达目的地。通过限制数据报的最大跳数,TTL可以确保数据报在有限的时间内能够到达目标主机或被丢弃,以避免网络资源的浪费和延迟。

    操作系统

    虚拟地址是怎么转化到物理地址的?

    图片图片

    虚拟地址到物理地址的转换是通过操作系统中的内存管理单元(MMU,Memory Management Unit)来完成的。下面是一般的虚拟地址到物理地址转换过程:

    • 程序发出内存访问请求时,使用虚拟地址进行访问。
    • 虚拟地址被传递给MMU进行处理。
    • MMU中的地址映射表(页表)被用来将虚拟地址转换为物理地址。页表是一种数据结构,用于存储虚拟地址和物理地址之间的映射关系。
    • MMU根据页表中的映射关系,将虚拟地址转换为对应的物理地址。
    • 转换后的物理地址被传递给内存系统,用于实际的内存访问操作。

    页表是怎么构成的?

    页表是一种数据结构,用于存储虚拟地址和物理地址之间的映射关系。多级页表将页表分为多个层级,每个层级的页表项存储下一级页表的物理地址。通过多级索引,可以逐级查找,最终找到对应的物理页。

    对于 64 位的系统,主要有四级目录,分别是:

    • 全局页目录项 PGD
    • 上层页目录项 PUD
    • 中间页目录项 PMD
    • 页表项 PTE

    图片图片

    进程间通信有哪些?

    • 管道(Pipe):管道是一种半双工的通信方式,可以在父子进程或者具有亲缘关系的进程之间进行通信。管道可以是匿名管道(使用pipe函数创建)或有名管道(使用mkfifo函数创建)。
    • 信号(Signal):信号是一种异步的通信方式,用于通知进程发生了某个事件。进程可以通过系统调用signal或sigaction来注册信号处理函数,当接收到特定信号时,会调用相应的处理函数进行处理。
    • 共享内存(Shared Memory):共享内存是一种高效的通信方式,允许多个进程共享同一块物理内存区域。进程可以通过映射共享内存到自己的地址空间,实现对共享数据的读写。
    • 信号量(Semaphore):信号量是一种用于进程同步和互斥的机制。进程可以使用信号量来控制对共享资源的访问,实现进程之间的同步和互斥。
    • 消息队列(Message Queue):消息队列是一种有序的消息传递机制,进程可以通过消息队列发送和接收消息。消息队列提供了一种可靠的通信方式,可以实现进程之间的异步通信。
    • 套接字(Socket):套接字是一种网络编程接口,也可以用于进程间通信。进程可以通过套接字进行网络通信,也可以通过本地套接字(Unix Domain Socket)实现本地进程间通信。

    共享内存是怎么实现的?

    共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。

    图片图片

    操作系统原子操作怎么实现的?

    操作系统中的原子性操作是通过硬件和软件的支持来实现的。在多核处理器上,原子性操作需要保证在多个核心之间的并发执行中的正确性和一致性。

    硬件层面上,现代处理器提供了一些特殊的指令或机制来支持原子性操作,例如原子交换(atomic exchange)、原子比较并交换(atomic compare-and-swap)等。这些指令能够在执行期间禁止中断或其他核心的干扰,确保操作的原子性。

    软件层面上,操作系统提供了一些原子性操作的接口或函数,例如原子操作函数(atomic operation),它们使用了硬件提供的原子性指令来实现原子性操作。这些函数通常是在内核态下执行,可以保证在多个进程或线程之间的原子性。

    操作系统还可以使用锁机制来实现原子性操作。例如,互斥锁(mutex)可以用来保护共享资源的访问,只有持有锁的进程或线程可以访问共享资源,其他进程或线程需要等待锁的释放。通过锁的机制,可以保证对共享资源的原子性操作。

    算法

    • 算法:岛屿数量
    • 算法:股票

    其他

    • 你对自己的职业规划是什么?
    • 平时是怎么学习的?
    • 做项目过程中遇到的最大困难是什么?
    • 哪个项目你觉得收益最大?

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论