C++上位机软件工程师面试记录

C++上位机软件工程师面试记录 目录一1. Qt 常用多线程类有哪些2. Qt 多线程不重写 run() 如何使用3. TCP 粘包、半包问题如何处理4. TCP 与 UDP 有什么区别5. TCP 三次握手、四次挥手基本原理6. Modbus RTU 和 Modbus TCP 区别7. SQLite 数据库使用相关8. SQLite 常用查询语句与优化方案9. 数据库添加索引存在哪些缺点10. 项目中如何解决 UDP 丢包问题1. Qt 常用多线程类有哪些2. Qt 多线程不重写 run() 如何使用3. TCP 粘包、半包问题如何处理4. TCP 与 UDP 有什么区别5. TCP 三次握手、四次挥手基本原理6. Modbus RTU 和 Modbus TCP 区别7. SQLite 数据库使用相关8. SQLite 常用查询语句与优化方案9. 数据库添加索引存在哪些缺点10. 项目中如何解决 UDP 丢包问题一1. Qt 常用多线程类有哪些Qt 中实现多线程主要有三种方式QThread最基础、最底层的线程类适合需要长期在后台运行的守护任务如持续的串口数据接收或网络监听。QRunnable结合QThreadPool适合处理大量轻量级、短暂并发的计算任务。将任务封装在QRunnable中丢给线程池自动调度缺点是它不是QObject的子类不能直接使用信号与槽与 GUI 线程通信。QtConcurrent高级多线程 API。可以通过QtConcurrent::run()极其方便地将一个普通函数丢到子线程执行底层也是基于线程池的封装。2. Qt 多线程不重写 run() 如何使用在工业级 Qt 开发中强烈推荐使用“Worker-Object”模式而不是去重写run()。 具体做法创建一个继承自QObject的 Worker 类把耗时的业务逻辑写在它的槽函数中。实例化一个标准的QThread和一个 Worker 对象调用worker-moveToThread(thread)将对象移入子线程。最后通过信号与槽机制去触发 Worker 执行任务任务完成后再通过信号将结果返回给 GUI 主线程。这种做法完全贴合 Qt 的事件循环Event Loop机制安全且解耦。3. TCP 粘包、半包问题如何处理TCP 是面向字节流的协议底层没有“数据包”的边界概念所以在项目开发中必然会遇到粘包或半包。 我的处理方案是自定义应用层协议配合状态机机制。常见的做法是采用“包头 包体”格式。设计一个固定长度的包头其中包含特殊的同步标识符和“包体长度”字段。接收端开辟一个本地数据缓存区Buffer利用状态机解析先读取并校验定长的包头解析出后续包体需要多长只有当缓存区里的数据量大于等于这个总长度时才将这一整帧数据提取出来进行业务处理剩余数据留在缓存区等待下一次拼接。4. TCP 与 UDP 有什么区别TCP是面向连接的、可靠的流协议。它有确认应答、超时重传、拥塞控制等机制保证数据不丢包、不乱序。缺点是协议头部大建立连接有开销速度相对慢。工程上常用于传文件、下发严谨的控制指令。UDP是无连接的、尽最大努力交付的数据报协议。它不保证可靠性发出去就不管了可能丢包或乱序。优点是开销极小速度快实时性好。常用于视频流传输、心跳包或高频的设备状态广播。5. TCP 三次握手、四次挥手基本原理三次握手建立连接客户端发 SYN服务端回 SYNACK客户端再回 ACK。核心目的是验证双方的发送和接收能力都正常并同步双方的初始序列号为可靠传输打好基础。四次挥手断开连接因为 TCP 是全双工的双向都可以发数据所以需要单向分别关闭通道。一方发 FIN 申请关闭它的发送通道另一方回 ACK 确认等另一方把手里剩下的数据也发完后也发 FIN 申请关闭最先发起方回 ACK并等待 2MSL报文最大生存时间后彻底断开防止最后的 ACK 丢失。6. Modbus RTU 和 Modbus TCP 区别Modbus 是一种极为经典的工业控制协议。底层物理链路不同RTU 通常基于串口通信如 RS-485/RS-232而 TCP 基于以太网和 TCP/IP 协议栈。报文结构不同RTU 报文包含“设备从站地址”以及结尾严格的“CRC 校验码”因为串口通信易受电磁干扰。而 Modbus TCP 报文去掉了从站地址和 CRC 校验因为底层的局域网交换机和 TCP 协议已经保证了准确到达和数据完整性并在报文头部增加了一个 7 字节的 MBAP 报文头包含事务元标识符等信息。7. SQLite 数据库使用相关SQLite 是一个非常轻量的、无服务端的本地关系型数据库无需配置数据就是一个本地文件。在上位机开发中非常适合用来做日志系统的持久化存储或设备本地参数配置。 在项目中结合 Qt 开发时通常使用QSqlDatabase建立连接用QSqlQuery执行 SQL 语句。如果涉及海量历史数据的可视化可以将其与 Qt 的 Model/View 架构如QSqlTableModel结合实现数据的按需懒加载和界面的流畅渲染。8. SQLite 常用查询语句与优化方案常用语句主要是SELECT,INSERT,UPDATE,DELETE配合WHERE过滤、ORDER BY排序和LIMIT分页。工程级优化方案使用事务Transaction这是 SQLite 最有效的提速手段。如果是批量插入上千条状态数据一定要用BEGIN和COMMIT将这些INSERT包裹起来能将磁盘 I/O 减少到仅有一次速度提升成百上千倍。预编译语句与绑定参数使用prepare()和bindValue()来执行 SQL。既可以防止 SQL 注入攻击又能提高数据库引擎解析语句的效率。合理建索引对经常用作WHERE查询条件的字段建立索引。9. 数据库添加索引存在哪些缺点索引虽然能大幅提高查询速度但代价是空间开销索引文件本身需要占用额外的物理存储空间。写降速与维护成本当对数据表进行INSERT、UPDATE、DELETE操作时数据库还需要动态去维护和更新索引树这会明显拖慢写入速度。因此不能滥用索引对于频繁更新的列、或者数据重复度极高如“性别”、“布尔值”的列不应建立索引。10. 项目中如何解决 UDP 丢包问题如果受限于硬件或网络环境必须使用 UDP但又要求不丢包就必须在应用层自己实现一套类似于 TCP 的可靠传输机制即 ARQ 机制序列号机制在发送的自定义包头中加入Sequence Number接收端据此判断包是否连续、有无乱序或重复。确认应答ACK接收端收到包后必须向发送端回复一个对应的 ACK 包。超时重传发送端维护一个定时器发包后如果在指定时间内没有收到 ACK就将该包重发。 在实际工程落地中如果我们不想从零手写通常会直接移植引入开源的KCP 协议它就是一套纯算法级的可靠 UDP 传输协议在网络拥堵时比 TCP 延迟更低。1. Qt 常用多线程类有哪些Qt 中实现多线程主要有三种方式QThread最基础、最底层的线程类适合需要长期在后台运行的守护任务如持续的串口数据接收或网络监听。QRunnable结合QThreadPool适合处理大量轻量级、短暂并发的计算任务。将任务封装在QRunnable中丢给线程池自动调度缺点是它不是QObject的子类不能直接使用信号与槽与 GUI 线程通信。QtConcurrent高级多线程 API。可以通过QtConcurrent::run()极其方便地将一个普通函数丢到子线程执行底层也是基于线程池的封装。2. Qt 多线程不重写 run() 如何使用在工业级 Qt 开发中强烈推荐使用“Worker-Object”模式而不是去重写run()。 具体做法创建一个继承自QObject的 Worker 类把耗时的业务逻辑写在它的槽函数中。实例化一个标准的QThread和一个 Worker 对象调用worker-moveToThread(thread)将对象移入子线程。最后通过信号与槽机制去触发 Worker 执行任务任务完成后再通过信号将结果返回给 GUI 主线程。这种做法完全贴合 Qt 的事件循环Event Loop机制安全且解耦。3. TCP 粘包、半包问题如何处理TCP 是面向字节流的协议底层没有“数据包”的边界概念所以在项目开发中必然会遇到粘包或半包。 我的处理方案是自定义应用层协议配合状态机机制。常见的做法是采用“包头 包体”格式。设计一个固定长度的包头其中包含特殊的同步标识符和“包体长度”字段。接收端开辟一个本地数据缓存区Buffer利用状态机解析先读取并校验定长的包头解析出后续包体需要多长只有当缓存区里的数据量大于等于这个总长度时才将这一整帧数据提取出来进行业务处理剩余数据留在缓存区等待下一次拼接。4. TCP 与 UDP 有什么区别TCP是面向连接的、可靠的流协议。它有确认应答、超时重传、拥塞控制等机制保证数据不丢包、不乱序。缺点是协议头部大建立连接有开销速度相对慢。工程上常用于传文件、下发严谨的控制指令。UDP是无连接的、尽最大努力交付的数据报协议。它不保证可靠性发出去就不管了可能丢包或乱序。优点是开销极小速度快实时性好。常用于视频流传输、心跳包或高频的设备状态广播。5. TCP 三次握手、四次挥手基本原理三次握手建立连接客户端发 SYN服务端回 SYNACK客户端再回 ACK。核心目的是验证双方的发送和接收能力都正常并同步双方的初始序列号为可靠传输打好基础。四次挥手断开连接因为 TCP 是全双工的双向都可以发数据所以需要单向分别关闭通道。一方发 FIN 申请关闭它的发送通道另一方回 ACK 确认等另一方把手里剩下的数据也发完后也发 FIN 申请关闭最先发起方回 ACK并等待 2MSL报文最大生存时间后彻底断开防止最后的 ACK 丢失。6. Modbus RTU 和 Modbus TCP 区别Modbus 是一种极为经典的工业控制协议。底层物理链路不同RTU 通常基于串口通信如 RS-485/RS-232而 TCP 基于以太网和 TCP/IP 协议栈。报文结构不同RTU 报文包含“设备从站地址”以及结尾严格的“CRC 校验码”因为串口通信易受电磁干扰。而 Modbus TCP 报文去掉了从站地址和 CRC 校验因为底层的局域网交换机和 TCP 协议已经保证了准确到达和数据完整性并在报文头部增加了一个 7 字节的 MBAP 报文头包含事务元标识符等信息。7. SQLite 数据库使用相关SQLite 是一个非常轻量的、无服务端的本地关系型数据库无需配置数据就是一个本地文件。在上位机开发中非常适合用来做日志系统的持久化存储或设备本地参数配置。 在项目中结合 Qt 开发时通常使用QSqlDatabase建立连接用QSqlQuery执行 SQL 语句。如果涉及海量历史数据的可视化可以将其与 Qt 的 Model/View 架构如QSqlTableModel结合实现数据的按需懒加载和界面的流畅渲染。8. SQLite 常用查询语句与优化方案常用语句主要是SELECT,INSERT,UPDATE,DELETE配合WHERE过滤、ORDER BY排序和LIMIT分页。工程级优化方案使用事务Transaction这是 SQLite 最有效的提速手段。如果是批量插入上千条状态数据一定要用BEGIN和COMMIT将这些INSERT包裹起来能将磁盘 I/O 减少到仅有一次速度提升成百上千倍。预编译语句与绑定参数使用prepare()和bindValue()来执行 SQL。既可以防止 SQL 注入攻击又能提高数据库引擎解析语句的效率。合理建索引对经常用作WHERE查询条件的字段建立索引。9. 数据库添加索引存在哪些缺点索引虽然能大幅提高查询速度但代价是空间开销索引文件本身需要占用额外的物理存储空间。写降速与维护成本当对数据表进行INSERT、UPDATE、DELETE操作时数据库还需要动态去维护和更新索引树这会明显拖慢写入速度。因此不能滥用索引对于频繁更新的列、或者数据重复度极高如“性别”、“布尔值”的列不应建立索引。10. 项目中如何解决 UDP 丢包问题如果受限于硬件或网络环境必须使用 UDP但又要求不丢包就必须在应用层自己实现一套类似于 TCP 的可靠传输机制即 ARQ 机制序列号机制在发送的自定义包头中加入Sequence Number接收端据此判断包是否连续、有无乱序或重复。确认应答ACK接收端收到包后必须向发送端回复一个对应的 ACK 包。超时重传发送端维护一个定时器发包后如果在指定时间内没有收到 ACK就将该包重发。 在实际工程落地中如果我们不想从零手写通常会直接移植引入开源的KCP 协议它就是一套纯算法级的可靠 UDP 传输协议在网络拥堵时比 TCP 延迟更低。11. 介绍Qt中事件循环机制。在 Qt 中事件循环的本质就是一个无限循环的死循环结构它的核心作用是拦截、分发和处理来自操作系统、硬件或者程序内部产生的各种事件以此来驱动整个 GUI 程序的运行。从底层机制来看当我们在主函数中调用QApplication::exec()时其实就是开启了这个事件循环对应底层QEventLoop类。 它的工作流程可以通俗地理解为一个‘处理队列’的过程产生与排队无论是用户的鼠标点击、键盘输入还是底层的网络数据到达、定时器超时都会被封装成QEvent对象放入事件队列中。分发主线程在这个死循环中不断检查队列。如果队列有事件就会将其取出交由具体的事件分发器如notify()去路由。处理事件最终会被派发给目标对象QObject及其子类的event()函数再由具体的事件处理函数比如mousePressEvent或者触发关联的槽函数来执行具体的业务逻辑。休眠如果队列为空事件循环并不会一直消耗 CPU而是将线程挂起休眠直到下一个新事件到来把它唤醒。”