后端开发面经系列--百度校招C++一面

百度校招C++一面

公众号:阿Q技术站

来源:https://www.nowcoder.com/feed/main/detail/494256d167d344e1b3764d39740c83a1

1、Linux用过哪些命令?

  1. ls(List):列出目录内容。

    例如:ls -l 显示详细信息,ls -a 显示所有文件(包括隐藏文件)。

  2. cd(Change Directory):切换目录。

    例如:cd /path/to/directory 进入指定目录,cd .. 返回上一级目录。

  3. pwd(Print Working Directory):显示当前工作目录的路径。

  4. mkdir(Make Directory):创建新目录。

    例如:mkdir new_directory 创建名为"new_directory"的新目录。

  5. touch:创建新文件。

    例如:touch new_file.txt 创建名为"new_file.txt"的新文件。

  6. rm(Remove):删除文件或目录。

    例如:rm file.txt 删除文件,rm -r directory 递归删除目录。

  7. cp(Copy):复制文件或目录。

    例如:cp file.txt new_location/ 复制文件,cp -r directory/ new_location/ 复制目录。

  8. mv(Move):移动文件或目录,也可用于重命名。

    例如:mv file.txt new_location/ 移动文件,mv old_name.txt new_name.txt 重命名文件。

  9. cat(Concatenate):查看文件内容。

    例如:cat file.txt 显示文件内容。

  10. moreless:分页显示文件内容。

    例如:more file.txt 分页显示文件内容。

  11. headtail:显示文件开头或结尾的内容。

    例如:head -n 10 file.txt 显示文件前10行,tail -n 5 file.txt 显示文件后5行。

  12. grep:搜索文件中的文本。

    例如:grep "search_text" file.txt 查找文件中包含"search_text"的行。

  13. find:在文件系统中查找文件或目录。

    例如:find /path/to/search -name "filename" 查找指定文件。

  14. ps(Process Status):显示当前运行的进程。

    例如:ps aux 显示所有进程的详细信息。

  15. kill:终止进程。

    例如:kill process_id 终止指定进程。

  16. top:实时监控系统性能和进程活动。

  17. df:显示磁盘空间使用情况。

    例如:df -h 以人类可读的方式显示磁盘使用情况。

  18. du:显示目录的磁盘使用情况。

    例如:du -h directory 以人类可读的方式显示目录的磁盘使用情况。

  19. wget:从网络下载文件。

    例如:wget http://example.com/file.zip 下载文件。

  20. scp:通过SSH协议进行安全文件传输。

    例如:scp local_file.txt user@remote_server:/path/to/destination 将文件从本地传输到远程服务器。

2、grep有哪些参数?

  1. -i:忽略大小写

    例如:grep -i "pattern" file.txt 会不区分大小写地搜索模式。

  2. -r-R:递归搜索

    例如:grep -r "pattern" /path/to/directory 会递归搜索指定目录下的所有文件。

  3. -l:只显示包含匹配模式的文件名

    例如:grep -l "pattern" * 会列出包含模式的文件名。

  4. -v:反向搜索,显示不匹配的行

    例如:grep -v "pattern" file.txt 会显示文件中不包含模式的所有行。

  5. -n:显示匹配行的行号

    例如:grep -n "pattern" file.txt 会显示每行匹配的行号。

  6. -c:只显示匹配的行数

    例如:grep -c "pattern" file.txt 会显示匹配模式的行数。

  7. -w:只匹配整个单词

    例如:grep -w "word" file.txt 会匹配整个单词 “word”,而不是包含该单词的字符串。

  8. -A:显示匹配行及其后面的若干行

    例如:grep -A 2 "pattern" file.txt 会显示匹配模式的行以及后面的 2 行。

  9. -B:显示匹配行及其前面的若干行

    例如:grep -B 2 "pattern" file.txt 会显示匹配模式的行以及前面的 2 行。

  10. -E:启用扩展正则表达式

    例如:grep -E "(pattern1|pattern2)" file.txt 可以使用正则表达式匹配多个模式。

  11. -f:从文件中读取模式

    例如:grep -f patterns.txt file.txt 会从文件 “patterns.txt” 中读取模式并搜索。

  12. --include:仅搜索指定文件类型

    例如:grep "pattern" --include="*.txt" /path/to/directory 会搜索指定类型的文件。

  13. --exclude:排除指定文件类型

    例如:grep "pattern" --exclude="*.log" /path/to/directory 会排除指定类型的文件。

  14. --color:高亮显示匹配文本

    例如:grep --color=auto "pattern" file.txt 会用颜色高亮显示匹配的文本。

3、sed和awk用过吗?

  1. sed

一种用于文本处理的流编辑器。

  • 替换文本: 可以使用正则表达式在文本中查找并替换匹配的内容。
sed 's/old_pattern/new_pattern/g' filename
  • 删除行或文本块: 可以删除包含特定文本的行或文本块。
sed '/pattern/d' filename
  • 插入、追加和编辑文本: 可以插入、追加或编辑文本行。
sed '2i\This is a new line' filename
  • 打印文本: 可以选择性地打印文本。
sed -n '/pattern/p' filename
  1. awk

一种文本处理工具,它基于一种处理文本文件的编程语言,awk 适合处理结构化的文本数据。

  • 文本分割和字段提取: awk 可以将每行文本分割成字段,然后你可以访问和操作这些字段。
awk -F',' '{print $1, $3}' filename
  • 数据过滤和处理: 你可以使用 awk 来筛选、转换和操作文本数据。
awk '$2 > 50 {print $1, $2}' filename
  • 自定义输出格式: awk 允许你以自定义的格式输出文本。
awk '{printf "Name: %s, Age: %d\n", $1, $3}' filename
  • 循环和条件语句: 你可以在 awk 脚本中使用循环和条件语句来进行更复杂的数据处理。
awk '{if ($2 > 50) print $1, $2}' filename

4、死锁解决?

死锁发生在不同进程或线程之间,其中每个进程或线程都在等待其他进程或线程所持有的资源,导致所有进程或线程都无法继续执行。

  1. 预防死锁:

破坏死锁的四个条件中的一个或多个来预防死锁。但不能破坏互斥条件,其他三个都可。

  • 资源分配顺序: 一种预防死锁的方法是规定资源的分配顺序,确保每个进程或线程按照相同的顺序请求资源。这样,可以避免循环等待的情况。
  • 资源申请前知道资源需求: 进程在请求资源前,可以预先知道它将需要哪些资源,并一次性申请所有资源。这有助于减少死锁的可能性。
  1. 避免死锁:

和预防死锁的区别就是,在资源动态分配过程中,用某种方式阻止系统进入不安全状态。比如银行家算法。

  • 银行家算法: 银行家算法是一种资源分配算法,用于避免死锁。它通过检查资源分配状态和资源需求,确保分配资源后系统不会陷入死锁。
  1. 检测和恢复:

允许系统在运行过程中发生死锁,但可已设置检测机构及时检测死锁的发生,并采取适当措施加以清除。

  • 死锁检测: 可以定期检测系统中是否存在死锁。如果检测到死锁,可以采取措施来解除死锁。
  • 死锁解除: 一旦检测到死锁,可以采取以下方法之一来解除死锁:
    • 终止某些进程或线程,以释放它们所占用的资源。
    • 通过资源的抢占,暂时剥夺某些进程或线程的资源,直到死锁解除为止。
  1. 超时和重试:

进程或线程可以设置超时机制,如果它在一定时间内未能获取所需的资源,就放弃并重试。这有助于避免进程无限期地等待资源。

  1. 资源释放:

如果一个进程或线程在等待资源时,发现它持有的资源被其他进程或线程请求,它可以释放这些资源,然后再次请求它们。

  1. 资源分配策略:

采用合适的资源分配策略,如动态资源分配,以根据系统的资源状况来分配资源。这可以减少死锁的发生。

5、内核态和用户态切换?

  1. 内核态

内核态是操作系统的高特权级别,用于执行操作系统内核代码。在内核态中,操作系统内核可以访问系统的所有硬件资源、执行特权指令以及对系统资源进行管理和控制。

  1. 用户态

用户态是操作系统中的一种较低特权级别,通常用于运行普通应用程序。在用户态中,应用程序只能访问自己的内存空间和受操作系统允许的系统资源。应用程序无法直接访问硬件设备或执行特权指令。

  1. 切换步骤
  • 中断和异常: 当应用程序需要访问特权资源或执行系统调用时,它会触发一个中断或异常。这可以是应用程序主动请求的,也可以是由硬件事件(例如时钟中断)引起的。
  • 保存上下文: 当发生中断或异常时,操作系统内核会保存应用程序的当前上下文,包括寄存器、程序计数器和堆栈指针等信息。这是为了在将来再次执行应用程序时能够恢复到相同的状态。
  • 切换到内核态: 操作系统内核会将处理器的特权级别从用户态切换到内核态,这通常涉及到修改处理器的状态标志位,以允许执行特权指令。
  • 执行内核代码: 一旦进入内核态,操作系统内核可以执行所需的操作,如访问硬件设备、管理内存或执行系统调用。
  • 恢复用户态: 当内核完成操作后,它会恢复应用程序的上下文,并将处理器的特权级别切换回用户态。

6、HTTP1.0、1.1、2.0、3.0?

HTTP是一种用于传输超文本的协议,它在不同版本中不断演化和改进,以满足不断增长的网络需求。

HTTP 1.0:

HTTP 1.0是最早的HTTP版本,定义于1996年。它的特点如下:

  1. 短连接: 每次请求/响应都需要建立和关闭TCP连接,导致了多次握手和释放的开销,降低了性能。
  2. 无状态: HTTP 1.0是无状态的,每个请求/响应都是独立的,服务器不会保存连接状态,需要在每次请求中包含所有信息。
  3. 明文传输: HTTP 1.0数据传输通常以明文方式进行,缺乏加密和安全性。
  4. HTTP头部: 头部信息较简单,只包含必要的元数据。
HTTP 1.1:

HTTP 1.1是HTTP 1.0的后续版本,于1999年发布。它引入了以下改进:

  1. 长连接: HTTP 1.1引入了持久连接,允许多个请求/响应共享一个TCP连接,减少了握手和释放的开销,提高了性能。
  2. 管道化: HTTP 1.1支持请求/响应的管道化,允许客户端发送多个请求而不等待响应,进一步提高了性能。
  3. Host头部: 引入了Host头部字段,允许在同一台服务器上托管多个域名,从而支持虚拟主机。
  4. 缓存控制: 引入了更精细的缓存控制机制,使缓存更有效。
HTTP/2.0:

HTTP/2.0是HTTP 1.1的进一步改进,于2015年发布。它引入了以下特性:

  1. 多路复用: HTTP/2.0允许多个请求/响应流共享一个TCP连接,通过二进制帧实现多路复用,提高了并发性能。
  2. 头部压缩: 引入了头部字段压缩,减小了每个请求的开销,减少了带宽占用。
  3. 服务器推送: 服务器可以主动将与请求相关的资源推送给客户端,减少了客户端请求的延迟。
  4. 优化流: HTTP/2.0通过优化请求和响应流的传输,提高了性能。
HTTP/3.0:

HTTP/3.0是最新的HTTP版本,于2020年发布。它采用了一种名为QUIC(Quick UDP Internet Connections)的新的传输协议,以进一步提高性能和安全性:

  1. 基于UDP: HTTP/3.0不再依赖于TCP,而是基于UDP传输,减少了连接建立的时延,提高了网络性能。
  2. 多路复用: 类似HTTP/2.0,HTTP/3.0支持多路复用,允许多个请求/响应流在一个连接上并发传输。
  3. 头部压缩: HTTP/3.0继续采用头部字段压缩,降低了请求开销。
  4. 连接迁移: HTTP/3.0支持在不同网络条件下迁移连接,以适应移动设备的切换。

总的来说,HTTP 1.0到HTTP/3.0的演化过程主要关注了性能、效率和安全性的提升。HTTP/2.0和HTTP/3.0引入了多路复用和头部压缩等重要特性,以改进传输效率和降低延迟。HTTP/3.0更进一步通过使用QUIC协议提供了更佳的性能和网络适应性。

7、HTTPS具体原理?

HTTPS是一种用于安全传输数据的协议,它在HTTP的基础上通过加密通信来保护数据的机密性和完整性。

  1. 加密通信:

HTTPS使用加密来保护数据的隐私。它通过使用加密算法来对数据进行加密和解密。主要的加密协议包括:

  • SSL: SSL是HTTPS早期采用的加密协议,目前已被TLS所取代。SSL协议使用了对称加密和非对称加密。客户端和服务器之间协商密钥,然后使用对称加密来加密和解密数据。
  • TLS: TLS是SSL的继任者,被广泛采用。TLS提供了更强大的加密和身份验证机制,包括支持各种密码套件,公钥基础设施,以及数字证书的使用。
  • 加密算法: HTTPS使用对称加密和非对称加密。对称加密使用相同的密钥来加密和解密数据,而非对称加密使用一对公钥和私钥。常见的对称加密算法包括AES,而常见的非对称加密算法包括RSA和ECC。
  1. 数字证书:

HTTPS的另一个关键组成部分是数字证书,它用于验证服务器的身份。

数字证书包括以下信息:

  • 服务器的公钥
  • 服务器的域名
  • 数字签名,由证书颁发机构(CA)签署

数字证书是通过证书颁发机构(CA)签署的,以证明服务器的身份。客户端在与服务器建立连接时会验证数字证书的有效性。如果证书有效,客户端将使用其中的公钥来加密通信数据,确保数据只能由服务器解密。

  1. 握手过程
  • 客户端向服务端发起第一次握手请求,告诉服务端客户端所支持的SSL的指定版本、加密算法及密钥长度等信息。
  • 服务端将自己的公钥发给数字证书认证机构,数字证书认证机构利用自己的私钥对服务器的公钥进行数字签名,并给服务器颁发公钥证书。
  • 服务端将证书发给客户端。
  • 客服端利用数字认证机构的公钥,向数字证书认证机构验证公钥证书上的数字签名,确认服务器公开密钥的真实性。
  • 客户端使用服务端的公开密钥加密自己生成的对称密钥,发给服务端。
  • 服务端收到后利用私钥解密信息,获得客户端发来的对称密钥。
  • 通信双方可用对称密钥来加密解密信息。

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

为什么握手是三次?

TCP建立连接时之所以只需要"三次握手",是因为在第二次"握手"过程中,服务器端发送给客户端的TCP报文是以SYN与ACK作为标志位的。SYN是请求连接标志,表示服务器端同意建立连接;ACK是确认报文,表示告诉客户端,服务器端收到了它的请求报文。

即SYN建立连接报文与ACK确认接收报文是在同一次"握手"当中传输的,所以"三次握手"不多也不少,正好让双方明确彼此信息互通。

为什么挥手是四次?

TCP释放连接时之所以需要“四次挥手”,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次"挥手"传输的。为何建立连接时一起传输,释放连接时却要分开传输?

  • 建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。
  • 释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。

9、输入一个地址每一层做了啥?

  1. 物理层:
    • 物理层负责将数字数据转换为模拟信号或数字信号,以便通过网络传输。
    • 在这一层,你的计算机将 IP 地址转换成电压、光信号或者其他物理信号,以便将数据传输到物理介质上,比如以太网电缆、Wi-Fi 信号等。
  2. 数据链路层:
    • 数据链路层负责将物理层传输的数据分割成帧(Frames),并添加帧首部和帧尾部,以便将数据在本地网络中传输。
    • 本地网络设备(如交换机)将数据帧从一个设备传输到另一个设备,保证数据在局域网内的传输。
  3. 网络层:
    • 网络层将数据帧封装成数据包(Packets)并添加源和目标 IP 地址。
    • 路由器会根据目标 IP 地址来选择最佳路径将数据包从本地网络传输到目标网络。
  4. 传输层:
    • 传输层负责将数据包封装成段(Segments),并添加源和目标端口号。
    • 传输层协议(如 TCP 或 UDP)负责控制数据流、错误检测和纠正、流量控制以及数据传输的可靠性。
  5. 会话层:
    • 会话层建立、管理和终止网络会话。
    • 在网络通信中,会话层可能会涉及到建立到目标服务器的连接,并在连接上进行数据传输。
  6. 表示层:
    • 表示层负责数据的编码、加密和压缩,以确保数据在通信过程中的完整性和安全性。
    • 这一层可能会涉及到数据格式的转换,以适应不同系统之间的差异。
  7. 应用层:
    • 应用层包括用户应用程序,它们使用网络协议与网络进行通信。
    • 在这个层次上,你的 Web 浏览器使用 HTTP 协议请求指定 IP 地址的网页,服务器接收请求并响应,提供页面内容。

10、python生成器?

生成器是 Python 中一种特殊的迭代器(Iterator)对象,它允许你按需生成值,而不是一次性生成所有值并将它们存储在内存中。生成器通常用于处理大数据集或无限序列,以避免占用大量内存。生成器的核心思想是延迟生成,它允许你在需要时逐个生成值。

在 Python 中,有两种创建生成器的方式:使用生成器表达式和使用函数定义生成器。

  1. 生成器表达式:

生成器表达式类似于列表推导,但是使用圆括号而不是方括号,并返回一个生成器对象。生成器表达式的语法如下:

(generator_expression)

示例:

# 生成一个包含1到10之间所有偶数的生成器
even_numbers = (x for x in range(1, 11) if x % 2 == 0)

# 使用生成器表达式生成值
for num in even_numbers:
    print(num)
  1. 使用函数定义生成器:

使用函数来创建生成器,这种方式更加灵活,因为它允许你在生成值的过程中编写更多的逻辑。生成器函数使用 yield 语句来生成值,当遇到 yield 语句时,函数的状态被保存,允许你在下一次迭代中继续执行函数。

示例:

def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

# 创建生成器对象
counter = count_up_to(5)

# 使用生成器函数生成值
for num in counter:
    print(num)

需要注意的是,生成器在每次迭代后会保持其状态,这使得它们非常适合处理大数据集或需要分阶段生成值的情况。 11、python协程和进程?

协程(Coroutines):

协程是一种轻量级的、用户级的线程,也称为微线程。它允许你在一个单一的线程中实现多个执行上下文,从而实现并发。在 Python 中,协程通常使用 asyncawait 关键字来实现,这是异步编程的一部分。协程的主要特点包括:

  1. 非抢占式:协程是协作式多任务处理的一种形式,它不是操作系统进行的线程或进程抢占式多任务处理。在协程中,程序员需要显式地控制何时让出 CPU 控制权。
  2. 低开销:协程比线程和进程更轻量级,因为它们不需要操作系统级的线程切换,而是在应用程序级别进行切换。
  3. 单线程内多协程:协程可以在单个线程内创建多个执行上下文,从而在一个线程中实现并发。
  4. 异步编程:协程通常与异步编程框架(如 asyncio)一起使用,用于处理非阻塞的 I/O 操作,例如网络请求和文件读写。

示例:

import asyncio

async def foo():
    await asyncio.sleep(1)
    print("Foo")

async def bar():
    await asyncio.sleep(2)
    print("Bar")

async def main():
    await asyncio.gather(foo(), bar())

asyncio.run(main())

进程(Process):

进程是操作系统级别的并发单位,每个进程都有独立的内存空间和系统资源。进程之间的通信通常需要使用特殊的机制,如进程间通信(IPC)。在 Python 中,可以使用 multiprocessing 模块来创建和管理多个进程。进程的主要特点包括:

  1. 独立内存空间:每个进程有自己的独立内存空间,不同进程之间的数据不会相互干扰。
  2. 抢占式:操作系统会自动分配 CPU 时间片给不同的进程,实现抢占式多任务处理。
  3. 多核利用:进程可以同时利用多个 CPU 核心,因此适用于 CPU 密集型任务。
  4. 独立性:进程之间的错误不会相互影响,一个进程崩溃不会导致其他进程崩溃。

示例:

from multiprocessing import Process

def foo():
    print("Foo")

def bar():
    print("Bar")

if __name__ == "__main__":
    p1 = Process(target=foo)
    p2 = Process(target=bar)

    p1.start()
    p2.start()

    p1.join()
    p2.join()

区别和应用场景:

  1. 性能和资源开销:协程通常比进程具有更低的性能开销,因为它们在同一个进程内运行,而不需要操作系统级的进程切换。进程具有更高的性能开销,但适用于需要充分利用多核 CPU 的 CPU 密集型任务。
  2. 通信和同步:协程通常使用异步编程框架来进行通信和同步,而进程需要使用 IPC 机制来实现通信。协程更适用于处理 I/O 密集型任务和异步编程。
  3. 独立性:进程是完全独立的,一个进程崩溃不会影响其他进程,而协程在同一个进程内共享相同的内存空间,因此一个协程的错误可能会影响其他协程。
  4. 多核利用:进程可以利用多核 CPU,而协程通常在单个核心上运行,除非使用多线程或多进程来充分利用多核。

12、流式输出?

流式输出(Stream Output)是指将数据流按特定格式输出到屏幕、文件或其他目标设备的过程。在C++中,流式输出通常是使用<<运算符结合流对象(如coutofstream等)来实现的。

  1. 流对象: 在C++中,流对象用于连接到不同的输出目标。常见的流对象包括:

    • cout:用于标准输出,通常是终端或控制台窗口。

    • cerr:用于标准错误输出,通常也是终端或控制台窗口。

    • ofstream:用于写入文件。

    • ostringstream:用于字符串流,将输出文本写入字符串变量。

  2. 流操作符 <<: 流操作符是<<,用于将数据插入到输出流中。这个运算符可以连接多个数据项,并按照格式化规则输出到流对象。

  3. 输出数据类型: 流式输出可以处理多种数据类型,包括整数、浮点数、字符、字符串和自定义类型等。

  4. 格式化输出: 可以使用流操作符的参数来指定输出的格式,例如设置输出的小数位数、对齐方式、宽度等。例如:

cout << "Value: " << 42 << " Float: " << fixed << setprecision(2) << 3.14159 << endl;
  1. 控制换行: 使用endl流操作符或'\n'控制字符来换行输出。

  2. 自定义输出运算符: 对于自定义的数据类型,可以通过重载<<运算符来指定如何输出该类型的对象。这允许你以自定义的方式输出数据。

示例:

#include <iostream>
#include <string>

int main() {
    int number = 42;
    double pi = 3.14159;
    std::string text = "Hello, World!";

    std::cout << "Number: " << number << std::endl;
    std::cout << "Pi: " << pi << std::endl;
    std::cout << "Text: " << text << std::endl;

    return 0;
}

13、适配器模式?

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许你将一个类的接口转换成客户端所期望的另一个接口。这种模式通常用于使已存在的类与其他类一起工作,而这些类的接口不兼容或不匹配。适配器模式是软件开发中常见的模式,它有助于解决不同类之间接口不一致的问题。

适配器模式涉及以下几个角色:

  1. 目标接口(Target):这是客户端所期望的接口,也是客户端代码直接调用的接口。适配器模式的目标是将适配器类适配成这个接口。
  2. 适配器(Adapter):适配器是一个类,它实现了目标接口,并包装了需要适配的类。它充当目标接口与需要适配的类之间的桥梁,使它们能够协同工作。适配器通常包含一个对需要适配的类的引用。
  3. 需要适配的类(Adaptee):这是需要适配的类,它有一个与目标接口不兼容的接口。

给个例子:

// 目标接口
class Target {
public:
    virtual void request() = 0;
};

// 需要适配的类
class Adaptee {
public:
    void specificRequest() {
        std::cout << "Adaptee's specific request." << std::endl;
    }
};

// 适配器
class Adapter : public Target {
private:
    Adaptee* adaptee;

public:
    Adapter(Adaptee* a) : adaptee(a) {}

    void request() override {
        adaptee->specificRequest();
    }
};

int main() {
    Adaptee* adaptee = new Adaptee();
    Target* adapter = new Adapter(adaptee);
    adapter->request();

    delete adaptee;
    delete adapter;

    return 0;
}

14、new和malloc差异?

  1. 语法差异:
    • new 是 C++ 中的运算符,而 malloc 是 C 和 C++ 中的库函数。
    • new 在分配内存时还会调用对象的构造函数,因此适用于动态对象的分配。
    • malloc 仅分配一块原始内存块,不会调用对象的构造函数。你需要显式调用构造函数,适用于分配原始数据块的情况。
  2. 类型安全性:
    • new 在分配内存时会考虑到对象的类型,因此它是类型安全的。如果你尝试分配一个类的对象,new 会自动调用构造函数,并返回正确类型的指针。
    • malloc 返回一个 void* 指针,不考虑对象类型。这可能导致类型不匹配或未初始化的对象。
  3. 内存块的大小:
    • new 需要知道对象的大小以正确分配内存,因此不需要指定要分配的字节数。这意味着它将分配足够大小的内存以容纳对象。
    • malloc 需要你明确指定要分配的字节数,它不考虑对象的大小。这可能导致分配的内存块过大或过小。
  4. 异常处理:
    • new 可以抛出 std::bad_alloc 异常,以处理内存不足的情况。
    • malloc 仅返回 NULL 指针,需要手动检查分配是否成功。

15、为啥要有析构函数?

用于释放对象占用的资源,清理对象的状态,并执行必要的清理工作。

  1. 释放资源: 在对象的生命周期结束时,特别是在动态分配内存、打开文件、建立网络连接等需要手动释放的资源时,析构函数用于释放这些资源,避免内存泄漏和资源泄漏。
  2. 清理状态: 析构函数用于清理对象的内部状态。这可以包括将对象恢复到初始状态、释放分配的缓冲区、断开连接、关闭文件等。这有助于确保对象在销毁时不会留下不一致或不合理的状态。
  3. 回收资源: 在某些情况下,析构函数可以被用于实现自动资源管理,例如使用智能指针。智能指针在析构函数中释放资源,从而确保资源的正确释放。
  4. 自定义清理逻辑: 对于用户自定义类,析构函数允许程序员定义自己的清理逻辑。这样,你可以确保对象销毁时执行特定的操作,比如写日志、通知其他对象等。
  5. 异常安全性: 析构函数在处理异常时起到重要作用。如果在析构函数中正确处理异常,可以确保资源的释放和状态的清理不会导致程序异常崩溃。

在C++中,析构函数的名称与类名称相同,前面加上波浪号(~)。它没有返回值,也不接受参数。当对象超出其作用域或通过 delete 运算符释放时,析构函数会自动调用。

16、智能指针内存泄漏的情况?

  1. 循环引用(Circular References): 这是最常见的智能指针内存泄漏情况之一。当两个或多个智能指针互相引用,形成一个循环引用时,这些对象将永远不会被销毁,因为它们的引用计数永远不会达到零。为了避免这种情况,可以使用std::weak_ptr来打破循环引用。
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b = b; // a和b互相引用
b->a = a;

解决方法:

struct A {
    std::shared_ptr<B> b;
};
struct B {
    std::weak_ptr<A> a; // 使用weak_ptr打破引用循环
};
  1. 裸指针(Raw Pointers): 如果你将智能指针中的裸指针传递给其他函数或存储为全局变量,可能导致内存泄漏。这是因为这些裸指针超出了智能指针的管理范围,而无法正确释放资源。确保在传递智能指针时不要泄露裸指针,并避免存储智能指针的裸指针。
std::shared_ptr<int> sp = std::make_shared<int>(42);
int* rawPtr = sp.get(); // 不应该将sp.get()泄露到其他函数或全局变量中

解决方法是,尽量避免使用裸指针,如果必须使用,确保在智能指针生存期内使用它。

  1. 循环依赖(Circular Dependencies): 这种情况发生在多个对象相互依赖的场景中。每个对象都持有其他对象的智能指针,可能导致内存泄漏。

解决方法是使用std::weak_ptr来打破循环依赖,就像处理循环引用一样。

  1. 手动resetrelease: 如果你手动调用resetrelease方法,可能会导致智能指针失去对资源的控制,从而导致内存泄漏。
std::shared_ptr<int> sp = std::make_shared<int>(42);
sp.reset(); // 此时sp不再管理资源,可能导致内存泄漏

要避免这种情况,不要手动重置智能指针,让其自然析构即可。

17、手撕:链表删除节点 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 返回删除后的链表的头节点。

思路:
  1. 首先,要检查链表是否为空。如果链表为空,就没有节点可删除。
  2. 遍历链表,找到要删除节点的前一个节点,以便将其 “跳过”。
  3. 修改前一个节点的 next 指针,将其指向删除节点的下一个节点,从而跳过要删除的节点。
  4. 释放删除节点的内存,以防止内存泄漏。
  5. 最后,返回链表的头节点,因为头节点可能在删除时发生变化。
示例代码:
#include <iostream>

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

ListNode* deleteNode(ListNode* head, int val) {
    // 处理空链表的情况
    if (head == nullptr) {
        return nullptr;
    }

    // 创建一个虚拟头节点,以便处理头节点可能被删除的情况
    ListNode* dummy = new ListNode(0);
    dummy->next = head;
    ListNode* prev = dummy;

    // 遍历链表查找要删除的节点
    while (head != nullptr) {
        if (head->val == val) {
            prev->next = head->next;
            delete head;
            break;
        }
        prev = head;
        head = head->next;
    }

    // 释放虚拟头节点,返回实际的头节点
    ListNode* newHead = dummy->next;
    delete dummy;
    return newHead;
}

// 打印链表
void printList(ListNode* head) {
    while (head != nullptr) {
        std::cout << head->val;
        head = head->next;
        if (head) {
            std::cout << " -> ";
        }
    }
    std::cout << std::endl;
}

int main() {
    // 创建一个链表: 1 -> 2 -> 3 -> 4
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    head->next->next->next = new ListNode(4);

    std::cout << "Original List: ";
    printList(head);

    int targetValue = 0;
    std::cin >> targetValue;
    head = deleteNode(head, targetValue);

    std::cout << "List after deleting " << targetValue << ": ";
    printList(head);

    // 释放链表内存
    while (head != nullptr) {
        ListNode* temp = head;
        head = head->next;
        delete temp;
    }

    return 0;
}
  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值