计算机面试八股(自整)

C/C++

编译运行过程

预处理(gcc -E,宏,头文件,注释,.i,ii),编译(-S,词法语法语义分析,.s),汇编(汇编代码转换成机器可执行的命令),链接(静态,将目标文件与静态库.a链接生成可执行文件;动态,只有库引用嵌入)

内存分段

代码段:可执行代码,只读的常量(某些编译器放到.rodata)

数据段:已初始化的全局变量,已初始化的静态变量

BSS:未初始化变量,未初始化的静态变量,自动初始化为0

堆:动态分配

栈:局部变量

程序的局部性原理

局部性原理(Locality Principle)是指程序在访问内存时,具有访问局部区域数据的倾向性。局部性原理可以大大提高缓存命中率,提高程序运行效率。

时间局部性:近期访问的数据或指令,在短时间内可能会被再次访问。

空间局部性:程序访问某个地址后,通常会访问其附近的地址。

管理资源生命周期的方法

手动管理:new,delete

RAII(Resource Acquisition Is Initialization):智能指针,vector,fstream,lock_guard和unique_lock

友元

在 C++ 中,friend 关键字用于允许非成员函数、外部函数或其他类访问私有(private)和受保护(protected)成员。这通常用于提高类的灵活性,但要谨慎使用,以免破坏封装性(Encapsulation)。

友元函数:

✅ 友元函数可以访问私有和受保护成员。

✅ 友元函数不是类的成员,因此不需要 this 指针。

✅ 友元函数可以是全局函数或其他类的成员函数。

友元类:

✅ 友元类的所有成员函数都可以访问目标类的私有成员。

✅ 友元关系是单向的,即 A 是 B 的友元类,但 B 不是 A 的友元。

✅ 友元类破坏了封装性,应谨慎使用。

友元类的成员函数:有时,我们不想把整个类设为友元,而只想让某个成员函数访问私有成员。

memcpy和memmove

memcpy 和 memmove 都是 C/C++ 标准库中的内存拷贝函数,用于在内存区域之间复制数据。

两者的主要区别在于是否支持重叠(overlapping)内存区域。

map,mutimap,unordered_map

map基于红黑树实现,有序,不重复,log(n);mutimap基于红黑树实现,有序,可重复log(n);unordered_map基于哈希表实现,无序,不重复,log(1)。

set,mutiset,unordered_set

set基于红黑树实现,有序,不重复,log(n);mutiset基于红黑树实现,有序,可重复log(n);unordered_set基于哈希表实现,无序,不重复,log(1)。

override与final

override 关键字用于显式标识一个虚函数是 重写(override)基类中的虚函数。它能够帮助编译器检查函数签名是否正确,确保我们确实是想要重写一个虚函数。如果重写的函数与基类的虚函数签名不匹配,编译器会给出错误提示。

final 关键字用于声明不允许进一步重写的虚函数或不允许继承的类。

  • 当 final 用于虚函数时,表示该函数在当前类中是最终实现,无法在派生类中被重写。
  • 当 final 用于类时,表示该类不能被继承。

explicit

防止构造函数隐式转换

C++11特性

权限

结构体默认为public,类默认为private,继承时同样。

static关键字

局部静态变量:局部静态变量是指在函数内部声明的静态变量。与普通局部变量不同,静态局部变量的生命周期持续到程序结束,而不是仅在函数调用期间存在。它们仅在第一次调用时初始化一次,之后保留其值。

全局静态变量:全局静态变量是指在函数外部声明的静态变量,它的作用域仅限于定义它的文件(翻译单元)。它不能在其他文件中访问。

静态函数:静态函数是指被 static 修饰的函数。它的作用域仅限于定义它的文件,不能在其他文件中调用。静态函数通常用于实现封装,防止其他文件直接调用。

静态数据成员:静态数据成员属于类而不是类的实例。它们对所有类的对象是共享的,因此静态数据成员的值是相同的。静态数据成员的声明通常放在类内,而定义通常放在类外。

静态成员函数:静态成员函数与静态数据成员类似,不依赖于类的任何实例,因此它们只能访问静态数据成员和其他静态成员函数,不能访问非静态成员。

操作系统

虚拟内存

虚拟内存为程序提供比实际物理内存更大的内存空间。虚拟内存就是把实际地址空间映射到虚拟地址空间的技术,这样就实现了内存隔离、内存扩展、物理内存管理、页面交换等技术。

分段与分页

分段:分段是一种将程序的内存分成多个逻辑段的内存管理方式。每个段通常代表程序的一个逻辑部分,如代码段、数据段(全局,静态)、BSS(未初始化变量)堆(动态分配)栈(局部变量)等。每个段都有一个段基址(段起始地址)和段长度(段的大小)。

分页:分页是一种将程序的内存分成固定大小的页面进行管理的方式。内存被划分为相同大小的页面,而物理内存被划分为相同大小的页框。分页的基本思想是将程序的虚拟地址空间映射到物理地址空间,页表查询。

用户态和内核态

用户态(User Mode)和内核态(Kernel Mode)是操作系统中最核心的概念之一,它们定义了程序运行的不同权限级别和资源访问范围。当程序需要执行特权操作(如读取文件、网络通信、内存分配)时,必须通过**系统调用(System Call)**进入内核态。

系统调用

系统调用是用户态程序与操作系统内核交互的接口,允许用户程序请求内核执行特权操作,如 文件操作、进程管理、网络通信、内存管理等。

中断,异常

中断和异常是两种不同的事件,二者都会导致CPU暂停当前的程序执行,转而去执行特定的处理程序。中断一般是由于外部设备或其他处理器导致的,一般是异步的,也就是说可以在任何时候发生,不会由当前的指令造成。比如键盘输入、鼠标输入、网络数据到达,来提醒CPU去处理这些事件。异常则是在当前CPU内部发生的,一般是同步的,由当前的指令造成。比如除数异常、访问非法内存地址、执行非法指令等,来提醒CPU去处理这些错误。

进程创建

fork,exec,clone

进程通信

管道:管道是一种半双工(只能一个方向传输)的通信机制,常用于父子进程之间的数据传输,通过内核提供的缓冲区进行。匿名管道仅用于亲缘进程间通信,命名管道可跨进程。

消息队列:消息队列是一个链表形式的消息集合,存储在内核中,由消息队列标识符引用。

信号:信号是一种异步通知机制,用于通知进程发生了某个事件。

共享内存: 共享内存是一段可以被多个进程访问的内存区域。本质上是让不同进程"看到"同一块物理内存,是最快的进程间通信方式之一(零拷贝,直接读写),通常与信号量或互斥机制配合使用,确保同步与互斥;

信号量:信号量是一个"计数器",用于控制多个进程或线程对共享资源的访问。

套接字:套接字是操作系统提供的通信接口,用于不同主机之间、同一主机内的不同进程之间进行数据交换。它就像数据传输的"网关",允许应用程序发送和接收数据。

进程调度

进程调度是操作系统中管理进程执行的关键机制,它决定了多个进程在 CPU 上执行的顺序。操作系统通过调度算法来分配 CPU 时间,使得系统能够合理、有效地利用 CPU 资源,提高系统的吞吐量和响应时间。

调度过程分为几个主要的阶段:

  • 进程创建:当新进程创建时,它会被放入就绪队列中。

  • 进程选择:操作系统的调度程序从就绪队列中选择一个进程进行调度。

  • 进程执行:被选择的进程开始执行。

  • 进程状态变更:

    • 就绪状态:进程已就绪,等待 CPU 执行。

    • 运行状态:进程正在执行。

    • 阻塞状态:进程因等待某些事件(如 I/O)而被挂起。

  • 进程切换:当当前运行的进程时间片耗尽或需要等待 I/O 时,操作系统会进行进程切换,将 CPU 交给下一个就绪的进程。

进程与线程区别

互斥和同步

锁(Lock)是并发编程中的一种基本机制,用于控制对共享资源的访问,以避免多个线程或进程在并发执行时发生竞争条件(Race Condition),确保数据的一致性和完整性。锁的本质是保证在同一时刻,只有一个线程能够访问临界区中的共享资源。

孤儿进程和僵尸进程

LD_PRELOAD

用户可以强制在程序启动时加载指定的共享库,优先于程序默认的库

I/O多路复用

一个单一的线程或进程中同时监视多个文件描述符(如套接字、文件、管道等),以确定这些文件描述符中的哪些已经准备好进行I/O操作(如读、写、或发生异常)。比如一个服务器程序,它需要处理多个客户端的网络请求。每个客户端连接到服务器的一个套接字。可以用多路复用技术(如 epoll)来"监视"所有这些套接字。为每个请求分配一个进程/线程的方式不合适,所以使用一个进程来维护多个 Socket。进程可以通过一个系统调用函数(select、poll、epoll)从内核中获取多个事件。

select 和 poll 都是内核通过轮询(遍历)的方式来检查多个文件描述符是否有 I/O 操作就绪(如可读、可写、异常情况等)。poll 没有文件描述符数量的限制,因为它采用的是动态数组的方式来存储文件描述符和事件。epoll 使用事件通知机制,一旦某个文件描述符的状态发生变化,内核会将这个事件通知给 epoll,使得程序不需要反复轮询所有文件描述符。

epoll实现的数据结构是红黑树和双向链表,红黑树用于存储和管理所有被监视的文件描述符(文件描述符通常是 socket)。当你向 epoll 实例添加、删除或修改一个文件描述符时,epoll 会将这个文件描述符插入或移除红黑树中。红黑树是一种自平衡的二叉搜索树,确保了插入、删除和查找操作的时间复杂度为 O(log n),这使得 epoll 能够高效地管理大量的文件描述符。双向链表用于存储就绪事件(即有 I/O 操作准备好的文件描述符)。当某个文件描述符就绪时,它会被加入到这个双向链表中。双向链表的插入和删除操作非常高效,时间复杂度为 O(1),这对于 epoll 处理大量并发连接时的性能至关重要。

计算机网络

OSI参考模型

http状态码

TCP与UDP

三次握手

bash 复制代码
客户端                             服务器
   | ------ SYN(seq=x) -------->     |
   |                                 |
   | <--- SYN-ACK(seq=y, ack=x+1) ---|
   |                                 |
   | ------ ACK(ack=y+1) -------->   |
连接建立完成

第一次握手:客户端 → 服务器

  • 发送 SYN 报文,seq = x,进入 SYN_SENT 状态。

  • 表示请求建立连接。

第二次握手:服务器 → 客户端

  • 收到 SYN 后,发送 SYN+ACK 报文,seq = y, ack = x+1。

  • 进入 SYN_RECV 状态,表示"我收到了你的请求,也准备好了"。

第三次握手:客户端 → 服务器

  • 客户端收到 SYN+ACK,发送 ACK 报文,ack = y+1。

  • 进入 ESTABLISHED 状态,服务器收到后也进入 ESTABLISHED 状态。

✅连接建立完成。

四次挥手

bash 复制代码
客户端                             服务器
   | ------ FIN(seq=u) -------->     |
   |                                 |
   | <--------- ACK(ack=u+1) --------|
   |                                 |
   | <------ FIN(seq=v) ------------ |
   |                                 |
   | --------- ACK(ack=v+1) -------> |
连接关闭完成

第一次挥手:客户端 → 服务器

  • 客户端发送 FIN,表示不再发送数据了,但仍可接收数据。

  • 进入 FIN_WAIT_1 状态。

第二次挥手:服务器 → 客户端

  • 收到 FIN,回复 ACK,表示"知道了"。

  • 进入 CLOSE_WAIT 状态。

第三次挥手:服务器 → 客户端

  • 等待服务器把该发的数据发完后,发送 FIN。

  • 进入 LAST_ACK 状态。

第四次挥手:客户端 → 服务器

  • 客户端收到 FIN 后发送 ACK,进入 TIME_WAIT 状态。

  • 等待 2MSL(最大报文生存时间)后,进入 CLOSED 状态。

  • 服务器收到 ACK 后,立即关闭连接。

为什么握手是三次、挥手是四次?

握手中,SYN 和 ACK 可以合并发送(第二次握手),所以只需三次。

挥手中,FIN 和 ACK 通常不能合并,因为服务端可能还有数据要发,因此需要四次。

数据结构

红黑树

红黑树(Red-Black Tree)是一种 自平衡二叉查找树(BST),用于在最坏情况下也能提供良好的查找、插入和删除效率。红黑树是 AVL树的轻量替代方案,相比 AVL 树插入和删除时的旋转更少,因此在写操作较多的场景中性能更优。

红黑树的性质(核心规则)

  • 每个节点要么是红色,要么是黑色。

  • 根节点是黑色。

  • 所有叶子节点(NIL)都是黑色。

  • 红色节点的子节点必须是黑色(不能连续两个红色节点)。

  • 从任一节点到其所有叶子节点的路径中,黑色节点数量相同(黑高一致)。

这些性质保证了树的平衡性:最长路径不会超过最短路径的2倍。

算法

动态规划

动态规划(Dynamic Programming,简称 DP)是一种用于求解最优化问题的算法设计思想,它通过将复杂问题分解成更小的子问题、保存子问题的解以避免重复计算,从而提升效率。

状态转移方程:

bash 复制代码
dp[i] = min(dp[i-1], dp[i-2]) + cost[i]

DFS,BFS

排序

冒泡排序(Bubble Sort):相邻两个数比较,把最大的"冒"到最后。

插入排序(Insertion Sort):从第二个元素开始,向前插入到合适的位置。

选择排序(Selection Sort):每次选择最小的元素,放在未排序序列的开头。

快速排序(Quick Sort):选一个"基准值",比它小的放左边,比它大的放右边,然后递归。

归并排序(Merge Sort):递归分成小块,然后合并排序。

堆排序(Heap Sort):先建一个大顶堆,然后不断将堆顶(最大值)放到末尾。

Rust

编程思想

Rust的编程思想安全性(所有权,不需要回收机制)、并发性和性能

数据库

四个特性,ACID

文件写入的过程

1.用户空间请求写入

当一个程序需要向文件写入数据时,它会通过系统调用请求操作系统执行文件写入操作。

  • 常见的系统调用:write()、fwrite()、open()(打开文件)

2.系统调用与内核交互

当程序调用 write() 等系统调用时,程序会进入内核态,操作系统将会进行以下操作:

  • 参数校验:检查写入操作的合法性,如文件描述符是否有效,文件是否存在,是否有写入权限等。

  • 缓冲区管理:操作系统会将数据缓存到内核中的缓冲区(通常是页缓存)。如果数据比较小,可能不会立即写入磁盘,而是先写入缓冲区,待缓冲区满或者文件关闭时再写入磁盘。

3.数据写入磁盘

当内核准备好写入数据时,会根据文件描述符指向的文件路径,执行实际的磁盘 I/O 操作。这个过程包含:

  • 文件系统管理:内核会使用文件系统来定位文件数据块(例如,EXT4、NTFS等)。文件系统管理元数据(如文件大小、位置、权限等)并决定数据应写入哪些磁盘块。

  • 磁盘调度:操作系统通过磁盘调度算法(如 FIFO、SSTF)管理写入请求的顺序,减少磁盘寻道时间,优化磁盘 I/O 性能。

  • 物理存储:数据被写入磁盘的物理存储介质中,通常是 HDD(机械硬盘)或 SSD(固态硬盘)。磁盘存储通常由磁头或闪存芯片进行管理。

4.同步与数据持久化

写入磁盘的过程可能会涉及同步机制以确保数据的持久性,特别是在发生故障时。

  • 同步写入:通过调用 fsync() 或 fdatasync() 系统调用,操作系统会强制将数据从内存中的缓存写入到磁盘,确保数据不丢失。通常,操作系统会在文件关闭时自动将缓存区的内容刷新到磁盘。

  • 异步写入:如果没有强制同步,数据可能会先存入内存缓存,稍后再异步写入磁盘。这种方式提高了性能,但存在数据丢失的风险(例如,系统崩溃时)。

5.文件关闭与缓冲区刷新

当文件写入完成并调用 close() 时,内核会执行以下操作:

  • 刷新缓存:如果缓冲区中还有未写入的数据,操作系统会将数据写入磁盘。

  • 释放文件描述符:释放与文件相关的资源,并更新文件的元数据(如修改时间等)。

6.文件系统的日志机制(可选)

一些现代文件系统(如 EXT4、XFS)使用日志(journal)机制来保证写入操作的原子性和持久性。每当数据被写入磁盘时,文件系统首先将操作记录到日志中,然后再执行实际的写入操作。如果系统崩溃,文件系统可以通过日志恢复到一致的状态。

相关推荐
十五年专注C++开发3 分钟前
面试题:C++11在C++98基础上增加了哪些内容?
开发语言·c++·设计模式·面试·stl·适配器模式
麦田里的守望者江8 分钟前
这个PC项目是去做还是不去做?
android·c++
尘寰ya25 分钟前
什么是原型污染?如何防止原型污染?
前端·面试·原型模式
小徐Chao努力29 分钟前
【解析】ReentrantLock锁、Syschronized锁面试点解析
java·面试·职场和发展·synchronized·
黑心萝卜三条杠31 分钟前
C++ 中结构体运算符重载与排序逻辑解析
c++
江城开朗的豌豆44 分钟前
CSS篇:解码W3C:Web标准背后的故事与前端开发实践
前端·css·面试
顾林海1 小时前
深度解析Hashtable工作原理
android·java·面试
江城开朗的豌豆1 小时前
CSS篇:前端开发必备:Flex布局在真实项目中的妙用
前端·css·面试
我不会JAVA!1 小时前
C++ 实现A*算法
c++·算法
小王努力学编程1 小时前
贪心算法学习C++
开发语言·c++·学习·算法·leetcode·贪心算法