527. 讲一下平衡二叉搜索树,删除非叶子节点后如何调整?
平衡二叉搜索树(比如AVL树或红黑树)是一种在每次插入和删除后能够自平衡的二叉搜索树,以保证树的高度尽可能地低,从而保证操作的性能。
删除非叶子节点后的调整步骤大致如下:
- 查找替代节点:被删除节点如果有两个子节点,一般用它的中序前驱(左子树中的最大值)或中序后继(右子树中的最小值)来替代该节点。
- 替换&删除:将查找到的替代节点的值复制到被删除节点(不实际移动节点),然后删除那个替代节点(这个替代节点最多只有一个子节点)。
- 平衡调整:删除替代节点可能破坏了树的平衡,所以需要从替代节点原本的位置向上回溯并调整树的平衡。调整包含旋转操作(单旋转或双旋转)以及可能的颜色变更(对于红黑树)。
528. 讲一下程序从源代码到可执行程序经过了哪些步骤?每个步骤都做了什么事?
-
预处理(Preprocessing):
- 处理源码文件中的预处理指令,比如#include、#define。
- 展开宏定义、删除注释,添加编译器需要的特定预处理文件。
-
编译(Compilation):
- 将预处理后的源代码转换成中间代码(通常是汇编语言)。
- 进行语法分析、语义分析、优化等处理。
-
汇编(Assembly):
- 将汇编语言转换为机器码,也就是具体指令,生成目标文件(.obj或.o文件)。
-
链接(Linking):
- 解决目标文件之间的引用,将多个目标文件与库文件合并成一个可执行文件。
- 处理外部依赖和函数库的调用问题,分配内存地址给各个函数和变量。
529. C++多线程了解么?如何定义多线程?如何让他们跑起来?
在C++中,可以通过several ways来定义并运行多线程。以下是使用pthread库和C++11标准中的线程库的基本概述:
使用pthread库:
-
定义线程:通过
pthread_create
函数创建一个新线程,需要传递一个指向函数的指针,该函数是线程将要执行的代码。 - 启动线程:调用pthread_create后,线程就开始运行了。
- 等待线程:通过pthread_join方法可以等待一个特定线程执行完毕。
使用C++11标准库:
- 定义线程:通过std::thread类创建一个新线程。
- 启动线程:线程对象在构建时就会开始执行。
- 等待线程:通过成员函数join可以确保主线程等待其他线程完成执行。
530. 有三个线程ABC,C必须在A和B运行完之后才能运行,应该怎样实现?
为了确保线程C在A和B之后运行,可以使用同步机制,例如条件变量、事件或者欺物(futures)和承诺(promises)。在C++11中,以下是一个使用C++11线程库和std::promise及std::future实现的例子。
- A和B线程在执行结束时向相应的std::promise对象设置一个值,从而修改关联的std::future对象的状态。
- C线程在开始执行工作前等待两个std::future对象,这样就能确保A和B线程完成后C线程才开始运行。
531. DNS解析的过程?
DNS解析的过程包括以下几个步骤:
- 本地缓存查询:检查是否有该域名的解析结果在本地缓存中。
- 递归查询:如果本地缓存没有,向配置的DNS服务器发送查询请求,DNS服务器将代表用户进行查询。
- 根域服务器查询:如果DNS服务器没有缓存,他会查询根域服务器。
- *域(TLD)服务器查询:根域服务器会返回负责该*域(如.com、.org)的服务器地址。
- 权威域名服务器查询:*域服务器将返回负责该域名的权威DNS服务器地址。
- 获取记录:权威DNS服务器返回请求的域名的IP地址。
- 缓存结果:接收方DNS服务器缓存解析结果,并将结果返回给客户,用户的操作系统也可能会缓存结果。
532. 系统DNS查询可能存在的缺陷
- 缓存污染:攻击者可能通过各种方式污染DNS缓存,导致用户被引导至恶意网站。
- 查询延迟:解析过程可能需要多次网络请求,如果服务器响应慢或者网络状况不佳,回导致解析延迟,影响用户体验。
- 单点故障:如果使用的DNS服务器出现故障,可能会导致无法解析域名。
- 隐私泄露:DNS请求暴露了用户的访问网站信息,不加密的DNS查询可以被第三方监听,造成隐私泄露。
- DNS劫持:运营商或者攻击者可以修改DNS解析结果,将用户导向其他网站。
533. DNS用的是什么协议?
DNS基本上用的是UDP协议,端口号是53.对于那些超过512字节的DNS响应或者需要可靠连接的情况(如区域传输),则会使用TCP协议。
534. 详细说一下TCP三次握手过程。第一、二、三次握手失败后分别会做什么事?序号和确认号怎么变的?
- 第一次握手(SYN):客户端发送一个SYN(同步)报文到服务器,并在报文中指定客户端的初始序列号(Client ISN)。此时客户端进入SYN_SENT状态。
- 第二次握手(SYN-ACK):服务器端收到客户端的SYN报文后会应答一个SYN-ACK报文,该报文包含服务器的初始序列号(Server ISN)和确认号(即客户端的初始序列号+1)。此时服务器进入SYN_RCVD状态。
- 第三次握手(ACK):客户端收到服务器的SYN-ACK报文后,发送一个ACK(确认)报文,确认号为服务器的初始序列号+1。此时连接建立成功,客户端和服务器进入ESTABLISHED状态。
如果在这个过程中出现失败:
- 第一次握手失败:如果客户端的SYN报文在网络中丢失,客户端会超时重传SYN报文,直到接收到服务器的SYN-ACK报文为止。
- 第二次握手失败:如果服务器的SYN-ACK报文在网络中丢失,服务器会超时重传SYN-ACK报文。同时客户端在一定时间未收到SYN-ACK报文后,也会重传SYN报文。
- 第三次握手失败:如果是客户端的ACK报文在网络中丢失,服务器将不能进入ESTABLISHED状态,而是会等待一段时间并超时重传SYN-ACK报文。客户端在发送ACK报文后如果直接开始数据传输,在服务器收到数据之后也会自动确认ACK,从而避免握手失败影响连接。
535. 关于序号和确认号的变化
- 序号:(Sequence NUmber),用来标识从TCP源端向目的端发送的字节流,每个字节都按传输顺序编号。
- 确认号(Acknowledgement Number):期望收到对方下一个报文段的第一个数据字节的序号,实际上是对方发送的数据序号加1。
536. 创建表名的SQL语句是什么?
CREATE TABLE 表名(
列名1 数据类型,
列名2 数据类型,
列名3 数据类型,
...
);
537. 往表中插入一条数据的SQL语句是什么?如果我要一次插入很多数据用什么方法?
插入单条数据的SQL格式如下:
INSERT INTO 表名(列名1,列名2,列名3,......)
VALUES(值1,值2,值3);
要一次插入多条数据,可以使用单个INSERT
语句,后跟多组值,每组值代表一条记录,格式如下:
INSERT INTO 表名 (列名1, 列名2, 列名3, ...)
VALUES
(值1a, 值2a, 值3a, ...),
(值1b, 值2b, 值3b, ...),
(...);
使用事务处理这些插入操作,以确保要么所有的插入都成功,要么在遇到错误时全部撤销,格式如下:
START TRANSACTION;
INSERT INTO 表名 (列名1, 列名2, 列名3, ...) VALUES (值1, 值2, 值3, ...);
INSERT INTO 表名 (列名1, 列名2, 列名3, ...) VALUES (值1, 值2, 值3, ...);
...
COMMIT;
如果中途出现错误,可以使用ROLLBACK
来回滚到事务开始前的状态。
538. 什么是面向对象编程
面向对象编程是一种编程范式,它使用“对象”来设计软件。对象是包含数据(通常称为属性或字段)和能够执行操作的函数(通常称为方法)的实体。OOP的核心概念包括封装、继承、多态:
- 封装:包装代码和数据到单一的单元中,隐藏内部实现细节,通过共有接口进行交互。
- 继承:允许新创建的对象继承现有对象的属性和方法。
- 多态:允许不同的对象以适合他们自己的方式响应同一消息(或方法调用)。
539. 说一下虚构造函数
构造函数不能被声明为虚函数。构造函数的任务是初始化对象的成员变量和设置对象的初始状态,而虚函数机制是用来实现运行时多态的,即在基类的指针或引用调用派生类的方法,因为在构造对象时,对象的类型是已知的,所以不需要虚构造函数来实现多态。
540. 说一下虚析构函数
虚析构函数用于确保当删除一个指向派生类对象的基类指针时,能够正确调用派生类的析构函数。如果基类的析构函数被声明为虚函数,当通过基类指针删除派生类对象时,会先调用派生类的析构函数,然后是基类的析构函数,从而保证对象的资源被适当释放。这是实现多态性资源管理的重要机制。
541. 内存泄漏和访问冲突的关系
内存泄漏和访问冲突都是程序中常见的错误类型,但他们指向不同的问题。
内存泄漏指的是程序未能释放不再使用的内存,导致内存逐渐耗尽。
访问冲突(也称竞争条件)指的是两个或多个进程或线程在没有适当同步的情况下同时访问某些共享资源,导致结果不确定或出错。
他们有关联:一个程序如果有访问冲突的问题,可能导致某些内存区域异常,这可能间接导致内存泄漏。例如,不一致的锁行为导致未能正确释放内存。但通常这两个问题是独立的,需要分别处理。
542. 介绍一下const,你是怎么用const的
const是一个关键字,用于声明一个常量或者一个不能修改的变量。
以下是一些常见的使用方式:
- 常量:const int age = 20 , 这里变量age被设定为常量,不能再次修改。
- 指向常量的指针:const int * p ,这里 p 是一个指向常整数的指针,通过 p 不能改变其指向的值,但是可以改变 p 的指向。
- 常指针:int * const p,这里 p 是一个指针常量,p 的指向不能改变,但可以改变p所指向的值。
- 常指针指向常量:const int * const p;这里,既不能改变指针p的指向,也不能通过p来改变其指向的值。
- 函数中的只读参数和返回值:i你可以在函数的参数声明以及返回值中使用const,使得函数在调用过程中不会改变参数的值,也使得返回对象不能被修改。
- 类成员函数:在类的成员函数后面声明const,比如void display() const;表示该成员函数内不能修改类的任何成员变量。