前言
今日面试收集了这些问题,其实我都是有思路的,我把我的思路都讲了出来,只是可能不太完整,有些坑坑巴巴不太理想。
1.多线程的消息队列
我的回答:创建一个队列,把要处理事务放进去先进先出给线程调用
标准回答:消息队列主要是一个队列进行消息存储作为缓冲(削峰填谷),一个锁确保安全访问,一个条件变量用来通知线程
2.多线程访问串口
面试官刚问这个问题的时候我就很奇怪,我听成多个线程访问串口怎么弄,因为我之前做串口方面的没做过这种,把我问懵了,原来是挖了个坑,串口是单通道通信的,压根不支持多个线程并发访问,幸亏没踩上去。
我的回答:用一个线程访问串口,其他的线程要传数据什么的就通过信号传给他
标准回答: 多线程同时读写会导致数据包错乱(如线程A写时线程B打断),通过一个专门的串口控制线程统一管理访问,其他线程通过信号槽机制间接操作
优化方案:
1.线程标识机制,每个请求携带发送者object指针用于结果回传
2.带超时的非阻塞锁,trylock(50)避免长时间阻塞
3.请求队列化
// 在SerialController中添加:
QQueue<Request> m_requestQueue;
QTimer m_processTimer;
void enqueueRequest(Request req) {
m_requestQueue.enqueue(req);
if(!m_processTimer.isActive())
m_processTimer.start();
}
void processNext() {
if(!m_mutex.tryLock()) return;
if(!m_requestQueue.isEmpty()) {
auto req = m_requestQueue.dequeue();
// 处理请求...
}
m_mutex.unlock();
}
4.错误恢复机制,串口断开自动重连
connect(m_serial, &QSerialPort::errorOccurred, [=]{
if(m_serial->error() == QSerialPort::ResourceError) {
m_serial->close();
QTimer::singleShot(1000, this, [=]{
if(m_serial->open(QIODevice::ReadWrite))
emit reconnected();
});
}
});
5.批处理机制
将多个小数据包合并发送
void scheduleWrite() {
if(m_writeBuffer.size() > 512 || m_flushTimer.elapsed() > 50) {
emit realWrite(m_writeBuffer);
m_writeBuffer.clear();
}
}
6.动态优先级队列
给关键请求设置最高优先级
7.数据压缩
使用qcompress处理大流量数据
8.硬件流控启用
m_serial->setFlowControl(QSerialPort::HardwareControl);
3.虚拟内存
我的回答:不知道
标准回答:虚拟内存是一种内存管理技术,它使得应用程序认为自己拥有连续可用的内存(即一个连续完整的地址空间),而实际上,物理内存通常被分割成多个碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。虚拟内存的主要目的包括扩大地址空间、内存保护、共享内存和更好的内存组织管理。
4.多线程条件变量
我的回答:不知道
我的思路:这一块确实忘了,之前研究锁的时候了解过是有一个机制,让资源足够的时候通知工作线程,和信号量搞混了惭愧
标准回答:条件变量是多线程基于锁的线程同步方法,读线程会处于wait状态,有一个共享变量,写线程修改完了以后回通知一个或多个读线程,通知哪个线程根据系统调度策略不能确定
5.q_object宏是干嘛的,和继承的那个有什么区别
我的回答:有这个宏就可以调用movetothered函数那些,后面混淆了答不出来
标准回答:q_object宏为程序提供了元对象系统,信号与槽机制,动态属性系统,事件和事件过滤器,翻译和国际化这些功能,他会自动解析并创建moc文件。
和继承q_object的区别:继承q_object相当于是一个智能手机,他提供基础的qt对象能力,而q_object宏相当于是应用商店,提供信号槽这些功能可以使用,有宏没继承会报错,有继承没宏不会报错,但是信号槽不能使用
6.时间复杂度和空间复杂度
我的回答:程序运行一个周期所需时间,空间复杂度忘了
标准回答:时间复杂度主要是衡量算法的运行次数,空间复杂度主要是衡量一个算法所需要的额外空间(一个程序需要额外定义变量的个数)
7.cannys函数参数
我的回答:忘了
标准回答:
void cv::Canny(
InputArray image, // 输入图像(单通道灰度图)
OutputArray edges, // 输出边缘图(二值图像,边缘为白色)
double threshold1, // 低阈值
double threshold2, // 高阈值
int apertureSize = 3, // Sobel 算子孔径大小
bool L2gradient = false // 梯度计算方式
);
OpenCV 中的 Canny()
函数是经典的边缘检测算法(由 John F. Canny 提出),其核心是通过多阶段处理提取图像中的强边缘。以下是其参数详解及使用指南:
📌 函数原型
void cv::Canny(
InputArray image, // 输入图像(单通道灰度图)
OutputArray edges, // 输出边缘图(二值图像,边缘为白色)
double threshold1, // 低阈值
double threshold2, // 高阈值
int apertureSize = 3, // Sobel 算子孔径大小
bool L2gradient = false // 梯度计算方式
);
🔍 参数深度解析
1. **image
:输入图像**
- 要求:必须为 8 位单通道灰度图像(如
CV_8UC1
)。 - 预处理建议:
- 若输入为彩色图,需先转为灰度:
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY)
- 可先进行高斯模糊降噪:
cv::GaussianBlur(gray, blurred, Size(5,5), 0)
- 若输入为彩色图,需先转为灰度:
2. **edges
:输出图像**
- 格式:与输入同尺寸的二值图(
CV_8U
),边缘像素值为255
(白色),非边缘为0
(黑色)。
3. **threshold1
与 threshold2
:双阈值**
- 作用:用于边缘连接的滞后阈值处理。
- 阈值关系:
threshold1 < threshold2
(否则函数会交换两者)。 - 工作流程:
graph LR A[梯度计算] --> B[非极大值抑制] B --> C{梯度值 ≥ threshold2?} C -->|是| D[确认为强边缘] C -->|否| E{梯度值 ≥ threshold1?} E -->|是| F[标记为弱边缘] E -->|否| G[抑制] D --> H[输出边缘] F --> I[与强边缘连通则保留]
- 经验值:
- 经典比例:
threshold2 : threshold1 ≈ 3 : 1
或2 : 1
- 示例:
(100, 200)
或(50, 150)
- 经典比例:
- 调参技巧:
- 高阈值
threshold2
控制主要边缘的完整性(值过高 → 边缘断裂)。 - 低阈值
threshold1
控制细节边缘的数量(值过低 → 噪声增多)。
- 高阈值
4. **apertureSize
:Sobel 算子孔径**
- 作用:计算图像梯度时使用的卷积核尺寸。
- 可选值:
3
,5
,7
(默认=3)。 - 影响:
- 较大的孔径(如 5 或 7)对噪声更鲁棒,但边缘定位稍模糊。
- 较小的孔径(如 3)边缘更精细,但对噪声敏感。
- 注意:在 OpenCV 4.x 中,若需使用 Scharr 算子(更精确的梯度计算),可设
apertureSize = -1
。
5. **L2gradient
:梯度幅值计算方式**
- **
false
(默认)**:使用 L1 范数(计算快)
|G| = |G_x| + |G_y|
- **
true
**:使用 L2 范数(更精确)
|G| = \sqrt{G_x^2 + G_y^2}
- 建议:
- 对精度要求高时选
true
(如医学图像)。 - 实时处理追求速度时选
false
。
- 对精度要求高时选
8.多线程如何异步
我的回答:使用锁的机制,使资源再一定情况下只能被一个线程访问,感觉和线程同步弄混了
标准回答:消息队列实现
总结
是不是很拉跨,这些我之前知道,用的太少了,就忘了,还是要温故而知新啊