计算机基础八股文

一、计算机网络

1、什么是奈奎斯特和香农定理

  1. 奈奎斯特定理的主要用途是确保在数字化信号处理中,采样频率足够高,以防止出现混叠(Aliasing)现象。混叠是指当采样频率不足时,高频信号会被误解为低频信号。该定理指出,为了完全恢复一个连续时间信号,我们需要以至少两倍于信号最高频率的采样率对该信号进行采样。
  2. 香农定理帮助确定了在给定信道条件下,可以传输的最大数据速率(信道容量)。在离散信号处理中,如果信号的带宽(频率范围)不超过采样频率的一半,那么我们可以以足够高的采样率对信号进行采样,以使原始信号可以完全恢复。

2、说说A主机给B主机发送消息,发生了什么?

首先A如何判断B是否和自己在一个局域网内?

答:可以根据IP地址和子网掩码计算、路由表能否直接到达、子网广播能否得到B的响应。

1)当A主机和B主机在一个局域网内:

· A主机检查自己的ARP缓存映射表中,是否有B主机IP地址对应的MAC地址,如果有,直接构建一个以太网帧,包含源MAC地址,目的MAC地址和要传输的数据,通过以太网交换机将数据包传输给B主机。

· A主机如果没有对应的映射缓存,会发送一个ARP请求广播,寻找对应IP地址的MAC地址,B主机收到这个广播消息,查看是发送给你主机的,就回传一个自己MAC地址给A主机。

· A主机知道B主机的MAC地址,直接发送数据帧即可,然后把IP地址和MAC地址映射表更新,完成通信。

2)当A主机和B主机不在一个局域网内:

· A主机知道自己默认网关(路由器)的IP地址,因为这通常在网络配置中设置的。然后通过ARP知道了默认网关的MAC地址,于是发送给它。

· 路由器开始工作:通过路由协议(RIP、OSPF、BGP)和路由算法(Dijkstra),将数据包发送给B主机所在的局域网。

· 局域网的路由器根据ARP技术,把数据发送给MAC对应的B主机,完成通信。

3、为什么不采用二次握手?

4、四次挥手中,最后一次挥手前为什么要等待2MS?

即:发送方等待2MSL,如果没有收到接收方发来的重传请求,说明发送方知道了接收方已经收到了确认,这个时候发送方就可以关闭了。

5、什么是可靠传输?

保证接收方进程从缓存区读出的字节流与发送方发出的字节流是完全一样的。

6、TCP实现可靠传输的机制

1)校验,和UDP一样,增加伪首部,通过二进制反码求和的方法来判断有没有发生错误。

2)序号

3)确认

4)重传(超时重传和快速重传)

7、TCP中的累计确认

1)发送方发送报文4和5,接收方只接受到了报文5

2)因为接收方没接收到4号报文,所以接收方发送确认号4,表示自己想要4号报文,但是刚刚收到的5号报文不丢弃,先存着

3)发送方收到了确认号4,重新发送。

4)接收方收到了4号报文,跟刚刚没丢弃的5号报文正好是连续的,这时候,只需要发送6号确认报文。

8、TCP中的超时重传和快速重传分别是什么?

分别设计两个概念:往返时间RTT三个冗余ACK

9、计算机网络体系结构错题总结:

1、链路的带宽仅衡量发送时延,与比特的传输时延无关。

2、网络体系结构是抽象的,不应该包括各层的实现细节(定义功能执行的方法)。

3、计算机网络的分类方法:网络使用的传输技术、覆盖范围合与规模。

4、计算机网络最基本的功能:流量控制、路由选择、传输控制

5、世界上第一个计算机网络是:ARPAnet

6、计算机网络拓扑结构主要取决于它的通信子网

7、TCP/IP模型中一共有 4

8、局域网合广域网之间的的差异:所覆盖的范围不同、所使用的协议不同

9、因特网采用的核心技术:TCP/IP

10、TCP/IP模型的网络层提供的是:无连接不可靠的数据报服务(可靠与连接又传输层提供)

11、OSI第六层是表示层:表示出用户看得懂的数据格式,实现与数据表示有关的功能

10、物理层错题总结:

1、CSMA/CD是半双工

2、奈奎斯特定理给出了在无噪声情况下码元的最大传输速率

3、香农定理给出了信息传输速率的极限

4、电路交换:建立一条被双方独占的物理通路

5、报文交换:存储转发、大小没有限制、网络节点要有较大的存储缓存空间

6、分组交换:存储转发、加速传输、可以采用数据报服务虚电路服务

7、数据报服务:无连接、不保证可靠性、出现故障会更新转发表

8、虚电路服务:建立一条虚连接、数据传输按顺序、完成传输后用户发出释放虚电路请求

9、数据报服务和虚电路服务都有网络层提供

10、集线器是多端口中继器,两者都无法隔离冲突域

11、数据链路层错题总结:

1、停止-等待协议和后退N帧协议收到的分组一定是有序的

2、HDLC协议采用的比特填充方法是比特串5个0后,加1

3、 数据链路层提供的服务不包括无确认的有连接服务:因为有连接,一定有确认

4、有关循环冗余校验:在使用多项式编码时,发送方和接收方必须预先商定一个生成多项式

5、检测 比特的错误,编码的海明距应该是:N + 1

6、纠正 比特的错误,编码的海明距应该是:2 * N + 1、

7、多路复用技术中:统计时分多路复用具有动态分配时隙的功能

8、TDM协议不会发生碰撞

9、CSMA/CA协议采用的是冲突避免功能,发送前监听信道,空闲才发,发送完一个帧后,需要等待一段时间,检查接收方是否发回帧的确认,若收到则无冲突,否则发生了冲突,重发该帧

10、CSMA/CD,最短帧长为64字节,去除首部18字节,数据长度最短为46字节,如果小于46字节,则需要填充

11、以太网采用总线拓扑结构;所有计算机共享一条总线;信息以广播方式发送;使用CSMA/CD协议对总线进行访问控制,来确保数据传输的方便性和可靠性;采用无连接的工作方式;不对发送的数据帧进行编号,也不要求对发送方发送确认;以太网提供尽最大努力交付,不提供可靠的服务;差错的纠正由传输层的TCP完成

12、以太网是不可靠的,但是发送方如果知道碰撞了,要重传;有一种情况,就算知道了碰撞,也不重传,那就是发送发数据已经发送完了,才知道碰撞了,这个时候不需要重传

13、以太网的最短帧长是为了检测冲突的,基本思路就是发送一帧的时间需要大于或等于信号沿着信道来回一趟的时间

14、全双工是两个信道,不会发生冲突,因此不受CSMA/CD限制

15、以太网的二进制后退算法中,第 i 次碰撞后,站点会在0和 2^i - 1 之间选择一个随机数

16、达到10次冲突后,随机数的区间固定在最大值1023上,以后不再增加,若连续超过16次冲突,则丢弃

17、网卡是用来实现以太网协议的,所以网卡主要实现了物理层和数据链路层的功能

18、PPP主要由3个部分组成:

        1)一个将IP数据报封装到串行链路的方法(一种成帧方法) 

        2)一个链路控制协议(LCP),用于建立、配置、测试数据链路连接,并在它们不需要时将它

        们释放

        3)一套网络控制协议(NCP),其中每个协议支持不同的网络层协议,用来建立和配置不同的

        网络层协议

19、在HDLC协议中,帧被分为3类,信息帧、监控帧、无编号帧    (无奸细)

20、在以太网上,“阻塞”信号的功能是:当发现冲突时,CSMA/CD发送一个“阻塞”信号,当所有站都检测到阻塞信号时,他们立即停止发送尝试

12、网络层错题总结:

1、由路由器互联的多个局域网中,要求每个局域网 物理层、数据链路层、网络层协议可以不同,但是网络层以上的高层协议必须相同

2、交换机必须理解数据链路层的协议、路由器必须理解网络层的协议

3、关于静态路由,用户可以随时配置路由表

4、RIP:距离-向量路由选择协议、OSPF:链路状态协议、BGP:路径-向量路由选择协议

5、路由选择协议的功能:获取网络拓扑结构信息、选择到达每个目的网络的最优路径、构建路由表。发现下一跳的物理地址不属于路由选择协议的功能

6、IP地址的计数单位:1 总 8 片 首 4       单位为字节

7、“路由收敛”是指:网络设备的路由表与网络拓扑结构保持一致

8、本地回路地址:127.0.0.1

9、IPv6不允许路由设备进行分片,也没有使用头部校验和域

10、RIP和OSPF是内部网关协议,BGP是外部网关协议

11、RIP:传输层UDP、OSPF:IP数据报、BGP:TCP连接

12、路由器的分组转发部分:交换结构、输入端口、输出端口组成

13、广播风暴的原因是网络拓扑结构设计和连接问题导致,需要交换机、路由器来隔离广播域

14、判断网络是否出现拥塞的依据是网络的吞吐量是否随着负载的增加而不断下降

13、传输层错题总结:

1、复用:发送方不同的应用进程都可以使用同一个传输层协议传送数据。

2、分用:接收方的传输层在剥去报文的首部后能够把这些数据正确交付到目的应用进程。

3、组播:仅应用于UDP,因为TCP是面向一对一,而组播是面向一对一或一对多。

为什么要使用组播:有的应用程序要把一个分组发送给多台目的主机,采用的方法不是让源主机给每台主机都发送一个单独的分组,而是让源主机把单个分组发送给一个组播地址,该组播地址标识一组主机。网络把这个分组复制后传递给该组中的每台主机。主机可以选择加入或离开一个分组,而且一台主机可以同时属于多个分组。

4、TCP头部和IP头部都是20B。

5、公共服务保留的端口号范围是0~1023。

6、可靠传输的“可靠”是指,使用确认机制来确保数据不丢失。

7、滑动窗口的实现是以字节为单位。

8、面向连接和面向无连接,不能说谁一定比谁快,连接可以省略信源地址,减小了数据冗余,但是建立虚链路要花一定的时间。

9、UDP报文头部包括:源UDP端口号、目的UDP端口号、报文长度、校验和。

不包括:目的地址。(伪首部包括源目的IP地址,UDP长度字段等,共12字节)

10、面向连接的服务因为开销大,所以效率和时间性能差。

11、客户/服务器领域、远程调用、实时   适用于UDP     远程登录需要用TCP

12、以太网的帧最大数据负载是1500B,IP首部长度为20B,故每个分片的数据字段长度为1480B。

13、四次挥手最后一次客户端发送完ACK后的状态是TIME_WAIT。

14、应用层错题总结:

1、手最客户机是面向用户的,服务器是面向任务的。

2、客户机通常位于前端,服务器通常位于后端。

3、WWW不是一种协议,而是应用层提供的一种最为重要和普及的服务。

4、IP地址和域名并非一一对应,因为一个主机可以有多个网卡连接在两个网络上,那么就具有两个IP地址,这两个IP地址就可能映射到同一个域名;如果一个主机具有两个域名管理机构分配的域名,则这两个域名就可能具有相同的IP地址。

5、HDCP封包在传输层,使用的是UDP,因此HDCP是无连接的。

6、WWW上每个网页都有一个唯一的地址,这些地址统称为统一资源定位符(URL)

7、URL是专为标识Internet网上资源位置而设置的一种编址方式。

8、DNS多数情况下使用UDP,有时候也使用TCP。

9、FTP的数据端口号是FTP控制端口号-1,如当控制端口为2222,则数据端口号为2221

10、能够接受请求并发送网页的计算机叫WWW服务器。

11、主域名服务器运行域名服务器软件,有域名数据库。

12、一个域名有且只有一个主域名服务器。

13、每一台主机都必须在授权域名服务器注册登记,授权域名服务器一定能够将其管辖的主机名转换为该主机的IP地址。

14、FTP协议本身不具备差错控制能力,所以必须使用TCP。

15、FTP协议可以在不同类型的操作系统之间传送文件。

16、FTP协议并不适合用在两台计算机之间共享读写文件。

17、POP3是使用明文来传输密码的,并不对密码进行加密。

18、电子邮件经过MIME扩展后,可以将非ASCII码的内容表示成ASCII码的内容,其中的base64编码方式不管是否是ASCII码字符,每3个字符用另外4个ASCII码字符来表示,因此一个99B的邮件大小为132B(99 / 3 * 4)

19、Hyperlink:在WWW服务中,用户的信息查询可以从一台Web服务器自动搜索到另一台Web服务器。

20、SMTP:只支持传输7比特ASCII内容;支持在邮件服务器之间发送邮件;支持从用户代理向邮件服务器发送邮件。

21、单纯的访问Web网页不会用到SMTP,SMTP只有使用邮件客户端发送邮件。

22、connection是非持久连接;keep-alive是持久连接。

23、FTP:控制连接存在于整个FTP会话过程中,数据连接在每次文件传输时才建立,传输结束就关闭;默认情况下FTP协议使用21端口进行控制连接,使用20端口进行数据连接,但是是否使用20端口建立数据连接与传输模式有关,主动方式使用20端口,被动方式由服务器和客户端自行协商决定。

24、MIME:由于SMTP只限于传送一定长度的7位ASCII码邮件,于是提出了通用因特网邮件扩充(Multipurpose Internet Mail Extensions)

25、有了MIME,用户可以发送非ASCII码信息,通过base64编码为MIME,然后再解码成7位的ASCII码,然后进行SMTP传输,接收方接收到后再编码7位ASCII码和base64编码的MIME,最终通过非ASCII码给接收方。

二、操作系统(主要是进程管理和内存管理)

1、操作系统的特性

并发(进程)、共享(资源)、虚拟(虚拟处理机,即多道并发、虚拟内存)、异步(进程运行速度不预知)

2、什么是系统调用

用户在程序中调用操作系统所提供的一些子功能,可以被看做特殊的公共子程序。

可分为以下几大类:设备管理、文件管理、进程控制、进程通信、内存管理

3、进程的概念和特征

为什么引入:多道程序环境下,并发执行,引入进程能更好地描述和控制程序的并发执行,实现操作系统的并发性和共享性。

进程是动态的,是程序的一次执行过程,因此,是一个动态的,过程性的概念。

进程实体包括:PCB(描述进程的基本情况和运行状态,是进程存在的唯一标识)、程序段、数据段(程序加工处理的原始数据,执行时产生的中间数据或最终结果)

处理机调度(没有引入线程)和资源分配的基本单位。

特征:动态性(创建、活动、暂停、终止)、并发性、独立性(进程实体独立运行)、异步性(不可预知前进)、结构性(每个进程都配备进程控制块PCB对其描述)

4、进程的状态

创建、就绪、运行、阻塞、挂起、结束

5、进程的创建、终止、阻塞、唤醒、切换的步骤:

创建:1)分配唯一的标识号和PCB。2)分配资源。3)初始化PCB(标志信息、处理及状态、进程优先级等)。4)插入到就绪队列。

终止:1)根据进程标识符检索PCB,查看进程状态,如果是运行状态就停止运行,把处理机资源分配给其他进程。2)若有子进程,终止其所有子进程。3)将这个进程的资源归还给父进程或操作系统。4)将PCB从PCB队列删除。

阻塞:1)根据进程标识号找到PCB,查看进程状态,若为运行状态,则保护现场并转为阻塞状态。2)把PCB插入到等待队列。

唤醒:1)在等待队列找到对应的PCB,从等待队列移除,设置状态为就绪状态。2)把PCB插入到就绪队列中,等待被处理机调度。

切换(从一个进程的运行切换到另一个进程运行):1)保存处理机上下文(程序计数器,其他寄存器),并更新PCB。2)把进程PCB移入到相应的队列,如就绪、阻塞等队列。3)选择另一个PCB开始运行其进程。

6、进程的通信

共享存储(共享内存)

消息传递:进程间的数据交换需要以格式化的消息为单位,利用操作系统提供的发送消息和接受消息两个原语进行数据交换。1)直接通信方式:发送进程把消息发送到接收进程的消息缓冲队列上。2)发送进程把消息发送到某个中间实体中,接收进程从中间实体中取得消息,这种也叫信箱通信方式。电子邮件系统。

管道通信:1)匿名管道:临时、单向(半双工)、父进程创建子进程时,通过系统调用创建。一个连接读进程与写进程的共享文件。数据以字节流的方式存入管道中。2)命名管道:持久、双向(全双工)、进程通过路径名来引用,可在不同时间段,由操作系统提供的命令或系统调用来创建,通过路径名来引用管道进行数据通信。

7、说说线程,

引入进程的目的是为了让多道程序并发,而引入线程是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。

线程可以理解为轻量级进程,是一个基本的CPU执行单元。

线程是进程的一个实体,由线程ID、程序计数器、寄存器集合和堆栈组成。

不占据系统资源,只有一点点运行时必不可少的资源。与同属一个进程的其他线程共享这个进程的全部资源。

一个线程可以创建和撤销另一个线程,也有就绪、阻塞、运行三个基本状态。

引入线程,进程只是资源分配的基本单位。

进程和线程不同点在于:调度、拥有资源、并发、系统开销、通信方式

通信方式:1)进程的通信方式提供过进程同步和互斥来保证数据的一致性。2)线程的通信可以直接读写进程数据段(如全局变量)来进行通信。

线程是一个轻型实体,不拥有系统资源,有唯一的标识符和线程控制块(记录了寄存器和栈等信息)

8、为什么要引入线程后,并发性就更好了?

由于有了线程,线程切换时,可能会发生进程切换,也可能不发生进程切换,那么平均下来,每次切换所需要的开销就会小了,这样一来,多线程并发的响应速度就会快了。

9、线程的实现方式

用户级线程:线程管理工作由应用程序完成,程序员使用线程库设计多线程程序。

内核级线程:线程管理工作由内核完成。

多线程模型:根据用户级线程和内核级线程的对应关系(多对一、一对一、多对多)

这样线程管理在用户空间,效率比较高;但是要注意并发性(多对一中,用户级一个线程阻塞,整个进程就阻塞);而一对一和多对多就不会,不影响别的线程运行;多对多是为了解决一对一会有太多的系统开销而设计的折中的方法。

10、作业调度、中级调度、进程调度

作业调度:从外存中的作业,给分配内存和IO设备,是它获得竞争处理机的权利。

中级调度:将暂时不能运行的进程,调至外存等待,也叫挂起。

进程调度:按照某种方法和策略从就绪队列选取一个进程,将处理机分配给它。

11、进程的调度方式

非剥夺式调度:简单、系统 开销小、不能用于分时和实时系统。

剥夺式调度:系统吞吐率和响应效率有明显好处,但剥夺式不是任意性的,要遵循一些原则,比如优先权、短进程优先、时间片。

12、调度有哪些评价指标

CPU利用率、系统吞吐量(单位时间CPU完成的作业数量)、周转时间(作业提交到完成经历的时间)、等待时间、响应时间(用户提交到系统首次产生响应)

13、调度算法

先来先服务

短作业优先(运行时间长短)

优先级(分为非剥夺和剥夺的方式;根据优先级是否能够改变,分为静态优先级和动态优先级)

高响应比优先((等待时间+要求服务时间) / 要求服务时间)、时间片轮转、

多级反馈队列(第1级和第n-1级都是先来先服务,最后一级是时间片轮转;能够缩短平均周转时间):1、第i+1级队列每个进程运行时分配的时间片长度是第i级的2倍;2、在第i级没运行完,就将它放到第i+1级队列的末尾;3、当第1级队列为空,才调度第2级的,依次往后。4、每个新进程进入内存后,首先将它放入第1级队列的末尾。

14、进程同步

概念:

在程序并发执行中,不同进程之间存在着不同的相互制约关系,为了协调进程之间相互制约关系,引入了进程同步。比如计算1+2*3,系统产生了一个加法进程一个乘法进程,因为操作系统是有异步性,所以要加上制约条件,让乘法进程先执行。

临界资源:

系统中有些资源一次只能为一个进程所使用,把这种资源叫临界资源,如一些物理设备:打印机。还有许多变量和数据都可以被若干进程共享,也属于临界资源。

对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区

进入临界区,会设置正在访问临界区的标志,阻止其他进程进入临界区。

同步:

同步可称为直接制约关系,协调多进程的执行顺序,就是进程之间相互合作。

如:为了完成某种任务创建了两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。

示例:A向缓冲区放数据,B不能访问而阻塞,A放完数据,B被唤醒;缓冲区满了,A被阻塞,B取走数据后,A被唤醒。

互斥:

互斥可称为间接制约关系,就是一个进程进入临界区,另一个进程必须等待它出来才能进去。

示例:A想用打印机,但是此时B在用,那么A就被阻塞,当B用完,A被唤醒为就绪状态。

为了禁止两个进程同时进入临界区,同步机制遵循下面的原则:

1)空闲让进

2)忙则等待

3)有限等待

4)让权等待:不能进入临界区,则立刻释放处理机,防止进程忙等。

如何实现进程同步:

1)互斥锁和条件变量:

2)信号:信号是一种轻量级的通信机制,允许一个进程通知另一个进程发生了某个事件。进程可以等待信号的到来,并采取相应的行动。

3)信号量:可以用来解决互斥与同步问题,用两个原语来表示,wait(S),signal(S),即PV操作。

一个信号量实现同步的例子:假设有一个信号量S,两个进程P1,P2,现在P2有一个语句y想使用P1中一个语句x的输出结果。

解:当P1运行完x,发送一个信号量S,即V(S),在P2的语句y前写一个P(S)即可,当收到了信号量S,即P1的x已经运行完,可以拿到得到的结果。

15、进程同步、互斥的区别和联系是什么

并发进程的执行会产生相互制约的关系。

互斥:进程之间竞争使用临界资源,只能让他们逐个使用,这种现象称为互斥,即竞争关系。

同步:进程之间协同完成任务,一个进程在关键点上等待另一个进程发来的消息,以便协同一致,即协作关系。

16、几个经典的进程同步问题:

1)生产者-消费者

2)读者-写者

3)哲学家进餐

4)吸烟者问题

17、死锁

原因:

1)系统资源的争夺

2)进程推进顺序非法

3)信号量使用不当

四个必要条件:

1)互斥条件:当资源被占用,别的进程想用只能等待。

2)不剥夺条件:资源不能剥夺。

3)请求和保持条件:进程保持了至少一个资源,但是又提出了新资源的请求,新资源被占用无法获取,该进程的请求被阻塞,且对自己已经占有的资源不放。

4)循环等待条件:等待链中每个进程已获得的资源同时被链中下一个进程所请求,即循环等待。

死锁的处理策略:

1)死锁预防:破坏四个必要条件之一或几个。

2)死锁避免:在资源动态分配过程中,找到一个安全序列,防止系统进入不安全状态。即在系统进行资源分配前,先计算此次资源分配的安全性,会不会发生死锁,如果不会,再分配。

安全序列:按照某种进程推进顺序,为每个进程分配所需的所有资源,进程完成任务后释放,这样能够使每个进程都可顺序地完成,则称这个进程推进顺序P1,P2...PN为安全序列。

不安全状态不是死锁状态,而是可能进入死锁状态。

银行家算法就是一个经典的求能否找到一组安全序列的算法。

3)死锁的检测和解除:

死锁总结:

18、进程和作业有啥区别(不太重要)

进程:系统资源分配的单位。

作业:用户使用计算机为了实现一串相关任务,通常把这一串任务称为作业。

19、程序的装入和链接

编译:由编译程序将用户源代码编译成若干个目标模块。编译后,每个目标模块都是从0单元开始编址,称为该目标模块的相对地址(逻辑地址)。

链接:由链接程序将编译后形成的一组目标模块,以及所需要的库函数链接在一起,形成一个完整的装入模块。

装入:由装入程序将装入模块装入内存运行。

20、静态链接与动态链接

静态链接:在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的可执行程序,以后不再拆开。

动态链接:对某些目标函数模块的链接,是在程序执行中需要该目标模块时,才对它进行的链接。其优点是便于修改和更新,便于实现对目标模块的共享。

21、连续分配管理方式

单一连续分配:内存中只有一道程序。

固定分区分配:多道程序的存储管理方式。有两种分配方法,大小相等和大小不相等。这种方法会造成内部碎片,即程序小于固定分区大小时,会占用一个完整的内存分配空间。

动态分区分配:不预先将内存划分,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。这样会造成外部碎片,可通过紧凑技术解决。原理是操作系统不时地对进程进行移动和整理。

1)首次适应:空闲分区以地址递增的次数链接,顺序查找第一个大小满足分区的要求。

2)最佳适应:空闲分区按容量递增形成分区链,找到第一个满足要求的空闲分区。

3)最坏适应:以容量递减的次序链接来找第一个满足的空闲分区。

4)邻近适应:即循环首次适应,从首次适应上次查找结束的位置开始找。

首次适应是最简单,最好,最快的。

22、非连续分配管理方式

基于分页存储管理方式:

1)把主存空间划分为大小相等且固定的块,块相对较小,作为主存的基本单位。每个进程也以块为单位进行划分。

2)进程中的块称为页,内存中的块称为页框。

3)页表存在内存中。用户程序中指定有哪些页,去页表中根据页号所对应的块号。

4)逻辑地址A to 物理地址B:

1.计算页号P = (A/L)和页内偏移W(A%L)

2.比较页号P和页表长度M,若P>=M,则产生越界中断,否则继续执行。

5)高数缓冲区:快表,不在内存中,可以加快查询速度。

6)为了加快速度和效率:还有二级页表。解决了只有一个页表时,页表太长。

基于分段存储管理方式:

1)按照进程中的自然段划分逻辑空间,例如用户进程由主程序、两个子程序、栈、一段数据组成。那么就把该进程划分为5个段,每段从0开始编址。

2)段内要求连续,段间不要求连续。

3)也有段表,里面存的有段号和段内偏移量,跟分页差不多。

基于段页式管理方式:

1)页式存储管理能有效地提高内存利用率,而分段存储管理能反映程序的逻辑结构并有利于段的共享。

2)首先分为若干个逻辑段,每段有自己的段号,然后再将每一段分为若干个大小固定的页。对内存空间的管理仍然和分页存储管理一样,将其分为若干个和页面大小相同的块。对内存的分配以存储块为单位。

3)段页式逻辑地址结构:段号S 页号P 页内偏移量W

4)系统为每个进程建立一张段表,每个分段有一张页表。段表表项中至少包括段号、页表长度、页表起始地址。页表表项中至少包括页号和块号。系统中还应该有一个段表寄存器,指出作业的段表起始地址和段表长度。

5)地址变换需要三次访存:段、页、根据物理地址访存。

23、虚拟内存

高速缓存技术——局部性原理

时间局部性:可能由于循环操作,某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。

空间局部性:由于某些指令是顺序存放,顺序执行,一旦程序访问了某个存储单元,不久之后,其附近的存储单元也将被访问。

虚拟存储器:由于系统提供了部分装入、请求调入和置换功能后,给用户的感觉是好像存在一个比实际物理内存大得多的存储器。虚拟存储器的大小由计算机的地址结构决定。

虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。

管理方式:请求分页存储管理、请求分段存储管理、请求段页式存储管理

硬件支持:页(段)表机制、中断机构、地址变换机构

地址变换流程:1)先检索快表。2)找到了就修改页表项中的访问位,再推算物理地址。3)没找到就去内存中查找页表,对比状态位,看看是否在内存中,不在就产生缺页中断,请求从外存中把该页调入内存。

24、虚拟内存有哪些页面置换算法

最佳置换算法:

先进先出置换算法:

最近最久未使用置换算法:

时钟(CLOCK)置换算法:

25、虚拟内存中页面分配策略

固定分配局部置换:

可变分配全局置换:

可变分配局部置换:

26、分段与分页管理对比

27、内存管理总览

28、磁盘调度算法:

1、先来先服务

2、最短寻找时间优先

3、扫描(SCAN)

4、循环扫描(Circular SCAN,C-SCAN)

29、进程管理错题总结:

1、线程包含CPU现场,是处理机调度的基本单位,可以独立执行程序

2、并发进程失去封闭性,是指:并发进程共享变量,其执行结果与速度有关(影响了执行结果)

3、多对一的线程模型中,当一个多线程进程中的某个线程被阻塞后,整个进程都将阻塞

4、线程中的栈指针对其他线程透明

5、全局变量只是对于同一进程而言

6、先来先服务有利于CPU繁忙型的作业

7、在动态优先权中,随着进程执行时间的增加,其优先权降低,与作业对系统资源要求的多少没有关系

8、时间片轮转是由时间配额决定的,是绝对可抢占的

9、在进程处于临界区时,只要不破坏临界资源的使用规则,是不会影响处理机调度的

10、高响应比优先可以满足短作业优先,且不会发生饥饿现象

11、要对并发进程进行同步的原因是因为并发进程是异步的

12、P、V操作是低级进程通信原语

13、管程定义了共享数据结构和各种进程在该数据结构上的全部操作

14、临界区是指访问临界资源A的那段代码,那么如果有5个并发进程,则共有5个操作共享变量A的代码段

15、公共队列可供多个进程使用,但是一次只可有一个程序使用,因此公共队列属于临界资源

16、并发进程之间的关系可能是无关的,也可能是有交往的

17、破坏死锁的请求和保持条件,采用预先静态分配方法,即进程在运行前,一次性申请完它所需要的全部资源,资源未满足前不把它投入运行,一旦投入运行后,资源一直归其所有

18、为了破坏循环等待条件,可以采用顺序资源分配法

19、死锁避免是在资源动态分配过程中,防止系统进入不安全状态

20、死锁检测:依次消除与不阻塞进程(资源够分配)相连的边,直到无边可消。若消除了所有边,则说明无死锁,否则就发生了死锁

21、解除死锁一般不采用从非死锁进程中抢夺资源

22、引入多道程序技术的前提条件之一是系统具有中断功能

23、死锁检测时检查的是资源有向图

24、当系统进入不安全状态时,可能进入死锁状态

25、只要系统处于安全状态,系统便可以避免进入死锁状态,因此系统中一定无死锁进程

26、死锁状态必定是不安全状态

30、内存管理错题总结:

1、编译后的程序需要经过链接才能装载,而链接后形成的目标程序中的地址也就是逻辑地址。

2、编址空间的大小取决于硬件的访存能力,一般由地址总线宽度决定。

3、在使用交换技术时,进程正在进行IO操作时不能换出主存,否则它的IO数据区将被新换入的进程占用,导致错误。

4、在存储管理中,采用覆盖与交换技术的目的是节省主存空间

5、内存保护需要由操作系统和硬件机构合作完成,以保证进程空间不被非法访问。

6、覆盖技术是早期在单一连续存储管理中使用的扩大存储容量的一种技术,同样可以用于固定分区分配的存储管理中。

7、程序装载分为两种:静态重定位和动态重定位。

8、静态重定位:在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业,一旦进入内存后,在整个运行期间不能在内存中移动,也不能再申请内存空间。

9、动态重定位:可以将程序分配到不连续的存储区,在程序运行之前可以只装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存。

10、多进程在主存中彼此互不干扰的环境下运行,操作系统是通过内存保护来实现的。

11、采用分页或分段管理后,提供给用户的物理地址空间不能确定(不知页、段表长度)

12、分页系统中的页面是操作系统所感知的。

13、对于重定位存储管理方式,应在整个系统中设置一个重定位寄存器。

14、采用段式存储管理时,一个程序如何分段是在用户编程时由程序员决定的。

15、程序的动态链接与程序的逻辑结构相关,分段存储管理将程序按照逻辑段进行划分的,因此有利于其动态链接。

16、操作系统实现分区存储管理代价最小,因为分段和分页存储管理需要特定的数据结构支持。

17、对外存对换区管理以提高换入换出速度为主要目标。

18、页面大,页表就少;页面小,页表就大,但是页内碎片少。

19、存储管理的目的有两个:方便用户、提高内存利用率。

20、对主存的访问,以字节或字为单位。

21、对主存的分配,随存储器的管理方案不同而异。

22、在分页存储管理中,主存的分配是:以物理块为单位进行;逻辑地址分配是按页为单位。

23、会产生内部碎片:分页虚拟存储管理、段页式分区管理、固定式分区管理。

24、虚拟存储只能基于非连续分配技术。

25、FIFO页面淘汰算法,当可供分配的页帧数增加时,缺页中断的次数可能增加也可能减少。

26、引起LRU算法的实现耗费高的原因是需要对所有的页进行排序。

27、页表项的合法位信息显示着本页面是否在内存中,也即决定了是否会发生页面故障。

28、使用覆盖、交换方法可以实现虚拟存储。

29、请求分页存储管理的主要特点是扩充了内存(基于局部性原理实现了以时间换空间的目的)。

30、提供虚拟存储技术的存储管理方法名字中,带有“请求”二字,如请求段式存储管理。是基于请求机制的。

31、快表在计算机系统中用于地址变换

32、当系统发生抖动,采取撤销部分进程;不可采取增加磁盘交换区的容量;不可采取提高用户进程的优先级。

33、能够加快虚实地址转换的是:增大快表(TLB)容量;让页表常驻内存(有些页表在外存)

34、增大交换区不能加快虚实地址转换,两者无关系。

35、虚拟内存页面分配策略有:固定分配局部置换;可变分配全局置换;可变分配局部置换。

36、在请求分页系统中,页面分配策略与页面置换策略不能组合使用的是:固定分配-全局置换。

三、iOS开发


贪吃蛇游戏逻辑:
1)在贪吃蛇游戏中,游戏地图是一个UIView,具有自定义的宽度和高度,同时包含记分板和装饰图片等元素。游戏内部使用了NSTimer(定时器)来控制游戏进程,这涉及到Runloop的概念,它类似于Unity中的Update函数,是一个持续运行的事件循环,用于处理用户界面事件和其他任务,以确保应用程序的响应性和流畅性。
2)贪吃蛇是游戏中的主要角色,它由多个关节组成。在每次移动时,从蛇的尾部开始,将前一个关节赋值给当前关节,蛇头则是当前移动方向的前一个位置。当贪吃蛇吃到食物时,会在蛇尾位置添加一个新的关节,并在地图上的一个空位置生成新的食物。所有的游戏元素,包括蛇的关节、食物等,都使用CALayer来表示,游戏每次移动距离都是单位距离(比如地图宽度是100长度是200,那么地图就是200×100,可以设置每个位置为10×10)。
3)游戏结束的判断条件包括判断蛇是否吃到了自己的身体或者蛇头是否超出了地图边界。
俄罗斯方块游戏逻辑:
1)在俄罗斯方块游戏中,同样有一个游戏地图和定时器。游戏包括七种不同形状的方块集合,每种方块由四个小方块组成。方块从地图的最上方开始向下落,每次移动一个地图单位的距离。在下落过程中,游戏会检查是否与地图边界或下方已堆积的方块发生重叠,如果有重叠,方块停止下落。停止后,游戏会检查整个地图,寻找是否有整行都被方块填满的情况,如果有,这些行上的方块将被消除,并释放存储空间。消除行后,上面的方块会下落以填补空隙。
2)和贪吃蛇一样,俄罗斯方块游戏中的方块、地图等元素也使用CALayer来表示。游戏的结束条件是判断地图中最高方块的行数是否超过了设定的阈值,如果超过了阈值,游戏将结束。
游戏交互:
游戏提供了两种交互方式(上下左右Button、UIGestureRecognizer),包括上下左右按钮和手势识别器。其中,手势识别器主要用于实现上下左右滑动操作,这是通过UISwipeGestureRecognizer来实现的。这些交互方式帮助玩家操控游戏中的元素,让游戏更加互动和有趣,代码如下:

UIGestureRecognizer

NSTimer
面试题:
1)说说UIView、UIButton和CALayer的区别?有什么联系?
UIView:UI视图中的可视元素,能看到的物体都是一个UIView,有一些属性,如宽高、位置等,能响应用户交互事件,如触摸、手势等。
UIButton:专门用于处理用户点击事件的视图,有不同的状态,如正常、高亮、选中等。常用于点击后触发某些事件(函数)的发生。
CALayer:这个是Core Animation框架的一部分,一个轻量级的二维图层对象,用于处理图形和动画。不是用户界面元素,通常不直接和用户交互。负责绘制和呈现视图内容,支持动画和过度效果。可以嵌套在UIView中,作为其图层的内容。CALayer可以作为UIView的backing layer(背后的图层),用于呈现UIView的内容。CALayer更节省性能,因为更接近图形硬件。
UIButton和CALayer都可以嵌套在UIView中,它们也都是继承自UIView
2)说说UIButton的继承链?
UIButton——UIControl——UIView——UIResponder——NSobject
UIControl:用于创建用户交互控件,如按钮、滑块等。提供了处理用户事件(点击、值改变等)的机制。
UIResponder:用于处理和响应用户事件,包括触摸、运动、遥控器事件等。
NSObject:一切的基类,包括对象的基本功能,如内存管理、方法调用等。
3)说说ARC和MRC的区别?
ARC和MRC都是对象引用计数
MRC:分为两种,一个是retain&release、一个是autorelease。两者都需要程序员来手动增加对象的引用计数。不同点在于:1、前者需要程序员每有一个retain都要有一个release与之对应。2、当你调用 autorelease 方法时,对象的引用计数不会立即减少,而是会将对象添加到当前自动释放池中,同时对象的引用计数会保持不变。3、当自动释放池的作用域结束时,系统会自动对池中的所有对象调用 release方法,减少它们的引用计数。4、如果某个对象的引用计数变为零(即没有强引用指向它),系统会立即释放该对象的内存。
ARC:自动引用计数,编译器自动插入引用计数代码,程序员不需要手动管理对象的引用计数。ARC 通过静态分析和编译器优化来自动管理对象的引用计数,开发者不需要显式调用 retain、release 或 autorelease。ARC 简化了内存管理,但仍然基于引用计数原理。需要注意的点:1、默认情况下所有的指针都是强指针。2、ARC的判断准则: 只要没有强指针指向对象, 对象就会释放。
4)说说GCD?
5)说说KVO/KVC?
6)说说Memory Management?
回答MRC和ARC相关内容即可,程序员需要考虑对象的引用计数,避免内存泄露(没有释放对象)和野指针问题(指针指向的对象被释放,但是指针没有设置为nil)。
7)说说Block?
1、Block和函数指针都可以用于封装一段代码,使其成为一个可执行的单元,从而促进代码的模块化和复用。都可以看成是一个代码片段。
2、函数指针类型和Block类型都可以作为变量和函数参数的类型。
3、Block与函数指针不同的地方在于,Block是一种对象,是数据类型;而函数指针是指向函数的指针,它只能表示特定函数的类型,不能包含额外的数据或上下文。
4、函数指针只能指向预先定义好的函数代码块(可以是其他文件里面定义,通过函数参数动态传入的),函数地址是在编译链接时就已经确定好的。而Block本质是数据类型。
5、函数里面只能访问全局变量,而Block代码块不光能访问全局变量,还可以访问上下文的局部变量,即当前栈内存和堆内存变量的可读性(当然通过__block访问指示符修饰的局部变量还可以在block代码块里面进行修改)。
6、需要注意的是:1)、默认情况下, 不可以在block中修改外界变量的值。2)、因为block中的变量和外界的变量并不是同一个变量,block中使用的外界变量是copy的, 所以在调用之前修改外界变量的值, 不会影响到block中copy的值。3)、没有添加__block是值传递,加上__block之后就是地址传递, 所以可以在block中修改外界变量的值。
7、block是存储在堆中还是栈中;默认情况下block存储在栈中, 如果对block进行一个copy操作, block会转移到堆中;如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作;但是如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain;如果在block中访问了外界的对象, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain;如果是在ARC开发中就需要在前面加上__weak
8、Block的优点:可以捕获上下文局部变量,不需要像函数一样传递参数;语法简洁,如下图,比起函数指针来说更好理解;在ARC的环境中,Block的内存管理由编译器自动处理,不需要手动管理块的内存;与OC和Swift语言有很好的集成;Block是一种闭包,封装代码段,本意上是遵循面向对象的思想的。

8)说说你所知道的UIKit?
9)说说Core Animation?
10)说说你对iOS开发了解?
11)说说OC中的copy和mutableCopy(shallow copy & deep copy)?
浅拷贝:OC中的浅拷贝分为对与可变对象或不可变对象,是否由mutable修饰,mutable objects是可变对象。浅拷贝就是创建一个指向同一个对象引用,不可变对象因为不能修改对象本身,所以可以安全的共享;对于可变对象来说,因为原对象和引用都是指向同一个对象,那么修改了浅拷贝的引用,也会影响原始对象。
深拷贝:创建一个全新的可变对象,包含和原始对象相同的内容,不共享引用,修改新拷贝的对象后,不会影响原始对象。
12)什么是响应者链?与手势的关系?
分为事件传递和响应传递两部分。
事件:就是比如用户有触摸或滑动这些交互类的操作等,这就叫来了一个事件。那么事件的传递顺序是从下往上,即先传递给最下面的父视图,然后依次往上传递。
响应:传递完后,就是开始响应了,响应的话是从上往下,从最上面的视图开始,往下找,即到达第一个能够响应的事件的,也即能够处理这个事件的,则调用这个视图(控件)的响应方法。一旦事件在某个视图被处理,就不会继续传递给其他视图。
手势识别器可以关联特定的视图,当触发手势,就执行关联视图的方法,但是不会影响响应者链的正常流程。
13)什么是 Runloop?
类似于Unity中的Update函数,是一个持续运行的事件循环,用于处理用户界面事件和其他任务,以确保应用程序的响应性和流畅性。
14)什么是Runtime?
暂无
15)OC 中的元类 meta-class 是什么?
元类(Meta-Class)是一种特殊的类,用于描述其他类的类。每个类在运行时都有一个对应的元类,用于存储该类的类方法(类方法是属于类本身而不是实例的方法)。通常不需要直接操作元类,因为编译器和运行时系统会自动处理元类的创建和维护。但你可以查看类的元类以及使用元类来调用类方法。类可以通过自己的类名来调用类方法,也可以先获取自己的元类(Meta-Class),来调用相同的类方法。
16)什么是自动释放池 autorelease pool?
是Objective-C中用于管理内存释放的机制之一,允许将对象标记为“自动释放”,从而告诉系统在稍后某个时刻释放这些对象,而不是立即释放;自动释放池通常用于控制内存管理,减少手动内存管理的复杂性,使代码更易于编写和维护;自动释放池可以协调内存管理,确保在适当的时候释放对象,以避免内存泄漏。自动释放池通常由系统在主运行循环(Main Runloop)中自动创建和管理。
17)OC中,为什么要使用property和synthesize?
为类的成员变量自动生成getter和setter方法的声明和实现,property还可以用于对成员变量修饰,比如retain,assign,nonatomic、strong、weak等。
18)知道atomic和nonatomic吗?
前者是property中默认的,受线程锁保护,同时只能有一个线程进入,不适合多线程开发;后者不受线程锁保护,不会使用锁来阻止多个线程同时访问属性。
19)isa指针是什么?有什么作用?对象为什么需要isa指针?

20)__weak与__strong有什么用?
弱引用和强引用,ARC中,当一个对象没有强引用指向的话,就会被释放。
21)Instance variable与Property有什么区别?
Instance variable就是普通的类的成员变量,如果在这个成员变量前加上property(属性修饰)的话,就看作是对普通的类的实例成员变量的高级封装和修饰。通过属性,可以更方便地定义实例变量的访问权限、内存管理语义、线程安全性等属性,无需手动编写 getter 和 setter 方法。属性可以为实例变量提供不同的存储修饰符(如strong、nonatomic)以及自定义 getter 和 setter 方法。
22)+[initialize]和+[load]有什么区别?
load方法在这个文件被程序装载时调用,与这个类是否被用到无关,因此load方法总是在main函数之前调用。
Initialize:当第一次执行某个类的方法时会执行这个类的load方法,即懒加载,没有用到这个类就不会调用,可以节省系统资源。
注:如果子类实现了initialize方法,就不会执行父类的了,直接执行子类本身的。如果分类实现了initialize方法,也不会再执行主类的。所以initialize方法的执行覆盖顺序是 分类 -> 子类 ->类。且只会有一个initialize方法被执行。
23)说说OC中的单例?

24)Block为什么会导致retain cycle?
Block 可能导致循环引用(retain cycle)的主要原因是,在 Block 内部引用了外部对象(通常是 self),而且这个 Block 又被这个外部对象强引用,这就形成了一个循环引用,导致对象无法释放,从而可能引发内存泄漏。
类似于两个对象之间的循环引用(retain cycle)
如果A对用要拥有B对象, 而B对应又要拥有A对象, 此时会形成循环retain
如何解决这个问题: 不要让A retain B, B retain A
让其中一方不要做retain操作即可
25)如何避免block retain cycle?
将block要访问的外部对象用__weak修饰:弱引用。

#include <algorithm>
#include<iostream>
#include<vector>

using namespace std;

void quicksort(vector<int>& numbers, int low, int high) {
	if (low >= high) return;
	int first = low, last = high, key = numbers[low];

	while (first < last) {
		//从后往前找比他小的放前面,从前往后找比他大的放在后面,
		//以第一个数为基准,必须先从后往前走,再从前往后走

		while (first < last && numbers[last] >= key) {
			last--;
		}
		if (first < last) {
			numbers[first++] = numbers[last];
		}

		while (first < last && numbers[first] <= key) {
			first++;
		}
		if (first < last) {
			numbers[last--] = numbers[first];
		}
	}
	numbers[first] = key;


	quicksort(numbers, low, first - 1);
	quicksort(numbers, first + 1, high);
}

#include <iostream>
#include <vector>

using namespace std;

void quicksort(vector<int>& arr, int left, int right) {
	if (left > right) {
		return;
	}

	int l = left;
	int r = right;
	int guard = arr[left];

	while (l < r) {
		while (l < r && arr[r] >= guard) {
			r--;
		}
		if (l < r) {
			arr[l++] = arr[r];
		}
		while (l < r && arr[l] <= guard) {
			l++;
		}
		if (l < r) {
			arr[r--] = arr[l];
		}
	}
	arr[l] = guard;
	quicksort(arr, left, l - 1);
	quicksort(arr, l + 1, right);
}
int main() {
	vector<int> numbers = { 5,6,3,2,7,8,9,1,4,0,0 };
	quicksort(numbers, 0, numbers.size() - 1);
	for (auto x : numbers) {
		cout << x << " ";
	}
	return 0;
}


#include <iostream>
#include <vector>

void merge(std::vector<int>& arr, int left, int middle, int right) {
    int n1 = middle - left + 1;
    int n2 = right - middle;

    std::vector<int> leftArr(n1);
    std::vector<int> rightArr(n2);

    for (int i = 0; i < n1; i++) {
        leftArr[i] = arr[left + i];
    }
    for (int j = 0; j < n2; j++) {
        rightArr[j] = arr[middle + 1 + j];
    }
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k] = leftArr[i];
            i++;
        }
        else {
            arr[k] = rightArr[j];
            j++;
        }
        k++;
    }
    while (i < n1) {
        arr[k] = leftArr[i];
        i++;
        k++;
    }
    while (j < n2) {
        arr[k] = rightArr[j];
        j++;
        k++;
    }
}
void mergeSort(std::vector<int>& arr, int left, int right) {
    if (left < right) {
        // 找到中间点
        int middle = left + (right - left) / 2;

        // 递归地对左半部分和右半部分进行排序
        mergeSort(arr, left, middle);
        mergeSort(arr, middle + 1, right);

        merge(arr, left, middle, right);
    }
}
int main() {
    std::vector<int> arr = { 12, 11, 13, 5, 6, 7 };
    int arrSize = arr.size();
    std::cout << "原始数组: ";
    for (int num : arr) {
        std::cout << num << " ";
    }

    mergeSort(arr, 0, arrSize - 1);
    std::cout << "\n排序后数组: ";
    for (int num : arr) {
        std::cout << num << " ";
    }
    return 0;
}


#include <iostream>
// 二叉树的结点定义
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode* left, TreeNode* right) : val(x), left(left), right(right) {}
};

// 链表的结点定义
struct ListNode {
    //int val;
    //ListNode* next;
    //ListNode() : val(0), next(nullptr) {}
    //ListNode(int x) : val(x), next(nullptr) {}
    //ListNode(int x, ListNode* next) : val(x), next(next) {}
    int val;
    ListNode* next;
    ListNode(int x) {
        val = x;
        next = nullptr;
    }
};

int main() {
    ListNode* node1 = new ListNode(1);
    ListNode* node2 = new ListNode(2);
    ListNode* node3 = new ListNode(3);
    ListNode* node4 = new ListNode(4);
    ListNode* node5 = new ListNode(5);
    
    node1->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = node5;

    ListNode* cur = node1;
    ListNode* pre = nullptr;
    ListNode* next = node1;

    while (cur) {
        next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }

    while (pre) {
        std:: cout << pre->val << std::endl;
        pre = pre->next;
    }
    return 0;
}

// 单例
#include <iostream>
using namespace std;

// 懒汉式加载
class Singleton {
public:
	static Singleton* getInstance() {
		if (instance == nullptr) {
			cout << "创建单例\n";
			instance = new Singleton();
			cout << "单例创建完成\n";
		}
		else {
			cout << "单例已存在\n";
		}
		return instance;
	}
private:
	static Singleton* instance;
	Singleton() {
		cout << "懒汉式单例构造函数\n";
	}
	~Singleton() {
		cout << "懒汉式单例析构函数\n";
	}
};

// 饿汉式加载
class singleton {
public: 
	static singleton* getInstance() {
		return instance;
	}

private:
	static singleton* instance;
	singleton() {
		cout << "饿汉式单例构造函数\n";
	}
	~singleton() {
		cout << "饿汉式单例析构函数\n";
	}
};

Singleton* Singleton::instance = nullptr;
singleton* singleton::instance = new singleton();

int main() {
	cout << "main 开始\n";
	Singleton* s1 = Singleton::getInstance();
	Singleton* s2 = Singleton::getInstance();
	Singleton* s3 = Singleton::getInstance();

	singleton* s4 = singleton::getInstance();
	singleton* s5 = singleton::getInstance();
	cout << "main 结束\n";
	return 0;
}

// 栈
#include <iostream>
#include <vector>

template <typename T>
class MyStack {
private:
    std::vector<T> data;
public:
    // 添加元素到栈顶
    void push(T item) {
        data.push_back(item);
    }
    // 移除并返回栈顶元素
    T pop() {
        if (empty()) {
            throw std::out_of_range("Stack is empty");
        }
        T topItem = data.back();
        data.pop_back();
        return topItem;
    }
    // 返回栈顶元素,不移除
    T top() const {
        if (empty()) {
            throw std::out_of_range("Stack is empty");
        }
        return data.back();
    }
    // 返回栈中元素的数量
    int size() const {
        return data.size();
    }
    // 判断栈是否为空
    bool empty() const {
        return data.empty();
    }
};
int main() {
    MyStack<char> myStack;
    myStack.push('a');
    myStack.push('b');
    myStack.push('c');

    while (!myStack.empty()) {
        std::cout << "Top: " << myStack.top() << std::endl;
        myStack.pop();
        std::cout << myStack.size() << std::endl;
    }
    return 0;
}


// 队列
#include <iostream>
#include <vector>
using namespace std;

class Queue {
private:
    std::vector<int> elements;
public:
    void push(int value) {
        elements.push_back(value);
    }
    void pop() {
        if (!elements.empty()) {
            elements.erase(elements.begin());
        }
    }
    int front() const {
        if (!elements.empty()) {
            return elements.front();
        }
        else {
            std::cerr << "Queue is empty." << std::endl;
            return -1;  // Return a default value if the queue is empty
        }
    }
    int back() const {
        if (!elements.empty()) {
            return elements.back();
        }
        else {
            std::cerr << "Queue is empty." << std::endl;
            return -1;  // Return a default value if the queue is empty
        }
    }
    bool empty() const {
        return elements.empty();
    }
};

int main() {
    Queue q;
    q.push(1);
    q.push(2);
    q.push(3);

    std::cout << "Front of the queue: " << q.front() << std::endl;
    std::cout << "Back of the queue: " << q.back() << std::endl;

    while (!q.empty()) {
        std::cout << "Dequeue: " << q.front() << std::endl;
        q.pop();
    }

    //vector<int> vec;
    //vec.push_back(1);
    //vec.push_back(2);
    //vec.push_back(3);

    //for (int i = 0; i < vec.size(); i++) {
    //    cout << vec[i] << endl;
    //}
    //vec.erase(vec.begin());
    //for (int i = 0; i < vec.size(); i++) {
    //    cout << vec[i] << endl;
    //}
    return 0;
}

// 两个队列实现栈
class MyStack {
public:
    queue<int> queue1;
    queue<int> queue2;
    MyStack() {}
    void push(int x) {
        queue2.push(x);
        while (!queue1.empty()) {
            queue2.push(queue1.front());
            queue1.pop();
        }
        swap(queue1, queue2);
    }
    int pop() {
        int r = queue1.front();
        queue1.pop();
        return r;
    }
    int top() {
        int r = queue1.front();
        return r;
    }
    bool empty() {
        return queue1.empty();
    }
};


#include <iostream>

// 基类 Person
class Person {
public:
    int age;
    virtual void eat() {
        std::cout << "Person is eating rice." << std::endl;
    }
};

// 派生类 Child
class Child : public Person {
public:
    void eat() override {
        std::cout << "Child is drinking milk." << std::endl;
    }
};

int main() {
    // 使用基类指针指向派生类对象
    Person* p = new Child();
    p->age = 10;

    // 调用的是派生类 Child 的 eat 方法
    p->eat();
    std::cout << p->age << std::endl;

    delete p; // 记得释放内存
    return 0;
}


// vector 和 list
#include <iostream>
#include <vector>
#include <list>

int main() {
    // 使用 vector
    std::vector<int> intVector;
    for (int i = 1; i <= 5; ++i) {
        intVector.push_back(i);  // 插入元素到 vector 尾部
    }

    std::cout << "Vector elements: ";
    for (const auto& element : intVector) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    // 使用 list
    std::list<int> intList;
    for (int i = 1; i <= 5; ++i) {
        intList.push_back(i);  // 插入元素到 list 尾部
    }

    std::cout << "List elements: ";
    for (const auto& element : intList) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    // 访问 vector 的元素
    std::cout << "Vector element at index 2: " << intVector[2] << std::endl;

    // 访问 list 的元素
    // 由于 list 不支持随机访问,需要使用迭代器
    std::list<int>::iterator it = intList.begin();
    std::advance(it, 2);
    std::cout << "List element at index 2: " << *it << std::endl;

    return 0;
}



// C++ 归并排序
#include <iostream>
#include <vector>
#include <unordered_map>
#include <map>
void merge(std::vector<int>& arr, int left, int middle, int right) {
    int n1 = middle - left + 1;
    int n2 = right - middle;

    std::vector<int> leftArr(n1);
    std::vector<int> rightArr(n2);

    for (int i = 0; i < n1; i++) {
        leftArr[i] = arr[left + i];
    }
    for (int j = 0; j < n2; j++) {
        rightArr[j] = arr[middle + 1 + j];
    }
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k] = leftArr[i];
            i++;
        }
        else {
            arr[k] = rightArr[j];
            j++;
        }
        k++;
    }
    while (i < n1) {
        arr[k] = leftArr[i];
        i++;
        k++;
    }

    while (j < n2) {
        arr[k] = rightArr[j];
        j++;
        k++;
    }
}
void mergeSort(std::vector<int>& arr, int left, int right) {
    if (left < right) {
        // 找到中间点
        int middle = left + (right - left) / 2;

        // 递归地对左半部分和右半部分进行排序
        mergeSort(arr, left, middle);
        mergeSort(arr, middle + 1, right);

        merge(arr, left, middle, right);
    }
}
int main() {
    std::vector<int> arr = { 12, 11, 13, 5, 6, 7 };
    int arrSize = arr.size();
    std::cout << "原始数组: ";
    for (int num : arr) {
        std::cout << num << " ";
    }

    mergeSort(arr, 0, arrSize - 1);
    std::cout << "\n排序后数组: ";
    for (int num : arr) {
        std::cout << num << " ";
    }
    return 0;
}




  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
操作系统与计算机基础面试八股文主要包括以下内容: 1. 计算机基础:包括计算机网络、数据结构、排序算法、操作系统、数据库等方面的知识。例如,计算机网络中的协议结构可以采用五层体系结构,其中应用层对应 OSI 的上三层,下四层和 OSI 相同。\[2\] 数据结构中的二叉树有不同的种类和遍历方式。排序算法可以包括冒泡排序、插入排序、选择排序等。 2. 操作系统:涉及操作系统的基本概念、进程管理、内存管理、文件系统等内容。例如,操作系统中的死锁及条件可以使用银行家算法来解决。\[1\] 3. 计算机语言:包括 C/C++、Java、Python 等编程语言的相关知识。例如,C++中的 struct 和 class 的区别,Java中的线程安全和多线程编程等。 4. 网络安全:涉及网络安全攻击的种类和防范措施。例如,安全攻击可以包括网络钓鱼、拒绝服务攻击、SQL 注入等。\[3\] 综上所述,操作系统与计算机基础面试八股文主要涵盖计算机基础知识、操作系统、计算机语言和网络安全等方面的内容。 #### 引用[.reference_title] - *1* [计算机基础面试备考知识点(八股文)](https://blog.csdn.net/weixin_44934424/article/details/115066562)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [计算机网络面经八股文](https://blog.csdn.net/XiaoFengsen/article/details/125940951)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值