当前位置: 首页 > news >正文

我的Qt八股文面试笔记1:信号与槽文件流操作

我的Qt八股文面试笔记

1. 聊一聊Qt 信号与槽的优势与不足

优势

  • 松散耦合 (Loose Coupling): 信号与槽机制使得发送者(发出信号的对象)和接收者(包含槽函数并响应信号的对象)之间无需直接了解彼此。发送者只知道发出一个信号,而不知道哪个或哪些对象会接收它;接收者也只知道它响应某个信号,而不知道是哪个对象发出的。这种解耦极大地提高了代码的可维护性和可复用性。
  • 类型安全 (Type Safety): 在连接信号和槽时,Qt 会检查信号和槽的参数类型是否兼容。如果不兼容,编译器会报错或者在运行时给出警告,从而避免了因类型不匹配导致的潜在错误。
  • 灵活性 (Flexibility):
    • 一个信号可以连接到多个槽。
    • 多个信号可以连接到同一个槽。
    • 一个信号可以连接到另一个信号(转发)。
    • 槽可以是任何 C++ 成员函数、Lambda 表达式或全局函数。
  • 易于使用和理解 (Easy to Use and Understand): 信号与槽的语法直观明了,通过 connect() 函数即可建立连接,易于学习和应用。它提供了一种清晰的事件处理模型,使得代码逻辑更易于理解。
  • 运行时连接 (Runtime Connection): 信号和槽的连接可以在运行时动态建立和断开,这使得程序行为可以根据需要进行调整。
  • 跨线程通信 (Thread-Safe Communication): Qt 提供了队列连接(Queued Connection)机制,使得在不同线程之间传递信号和槽调用成为可能,且是线程安全的。这大大简化了多线程编程中对象间通信的复杂性。

不足

  • 性能开销 (Performance Overhead): 相比于直接的函数调用,信号与槽的连接和调用会引入一定的运行时开销。这是因为信号的发射和槽的调用需要经过 Qt 的元对象系统(Meta-Object System)进行查找和分发。对于需要极高效率的底层操作,直接函数调用可能更合适。
  • 调试难度 (Debugging Complexity): 由于松散耦合的特性,当一个信号被发射时,可能难以直观地追踪到所有连接到该信号的槽函数。在复杂的系统中,这可能会给调试带来一定的挑战。Qt Creator 等 IDE 提供了一些工具来帮助调试信号与槽的连接,但仍然需要一定的经验。
  • 编译时检查的局限性 (Limited Compile-Time Checking): 尽管 Qt 5 引入了新的连接语法(使用函数指针),可以进行更好的编译时检查,但在使用传统字符串形式的连接时,如果信号或槽的名称拼写错误,或者参数类型不匹配,只有在运行时才会发现错误。
  • 不适用于所有场景 (Not Suitable for All Scenarios): 信号与槽主要用于对象之间的事件通知和通信。对于需要频繁、高效地传递大量数据或进行密集计算的场景,可能需要考虑其他更直接或更高性能的通信机制。

2. 说一说Qt 信号与槽的本质?

Qt 的信号与槽机制,从 本质上 来说,是一种基于 观察者模式(Observer Pattern)类型安全解耦事件处理和通信机制。它通过 Qt 的 元对象系统(Meta-Object System) 实现,允许对象在无需直接了解彼此的情况下进行通信。

观察者模式的实现

信号与槽完美地体现了观察者模式的核心思想:

  • 主题(Subject):在信号与槽中,发出 信号 的对象就是主题。它在状态改变时发出通知(信号),但并不知道谁会接收这些通知。
  • 观察者(Observer):包含 函数的对象就是观察者。它们注册(通过 connect 函数)对特定信号的兴趣,并在信号发出时被通知(槽函数被调用)。

元对象系统的基石

信号与槽的强大功能之所以能够实现,离不开 Qt 的 元对象系统。这个系统在编译时通过一个名为 MOC (Meta-Object Compiler) 的工具,为继承自 QObject 的类生成额外的 C++ 代码。这些生成的代码包含了关于类的元信息,例如:

  • 类名
  • 父类信息
  • 信号 (Signals):通过 signals 关键字标记的成员函数。
  • 槽 (Slots):通过 slots 关键字标记的成员函数。
  • 属性 (Properties):通过 Q_PROPERTY 宏定义的属性。
  • 可调用方法 (Invokable Methods):通过 Q_INVOKABLE 宏标记的成员函数。

这些元信息使得 Qt 能够在 运行时 动态地进行对象的内省(introspection),即查询对象的属性、方法和信号/槽。

运行时连接与分发

当你使用 QObject::connect() 函数连接一个信号和一个槽时,Qt 的元对象系统会完成以下工作:

  1. 查找信号和槽:通过前面 MOC 生成的元信息,Qt 会根据信号和槽的名称(或函数指针)在运行时查找对应的信号和槽。
  2. 验证类型兼容性:Qt 会检查信号和槽的参数类型是否兼容。虽然在旧的字符串连接方式中这种检查发生在运行时,但 Qt5 引入的函数指针连接方式允许在 编译时 进行更严格的类型检查。
  3. 建立内部映射:如果信号和槽是兼容的,Qt 就会在内部建立一个映射关系,记录哪个信号连接到哪个槽。
  4. 信号发射:当一个信号被发射时(通常通过调用信号函数),Qt 的元对象系统会查找所有连接到该信号的槽。
  5. 槽函数调用:对于每个连接的槽,Qt 会根据连接类型(直接连接、队列连接等)调用对应的槽函数,并将信号的参数传递给槽。

关键特性提炼

  • 解耦:信号发送者和槽接收者彼此独立,只通过信号这个抽象的接口进行交互。
  • 类型安全:确保信号和槽的参数类型匹配,减少运行时错误。
  • 异步/同步通信:支持直接连接(同步)和队列连接(异步,跨线程安全)。
  • 反射机制:元对象系统提供了运行时内省能力,是实现信号与槽的基础。

简而言之,Qt 信号与槽的本质就是一套高效、灵活且类型安全的事件驱动通信框架,它利用 元对象系统 在运行时建立和管理对象之间的联系,从而实现松散耦合的程序设计。


QObject::connect() 函数是 Qt 中连接信号和槽的核心。它有多个重载版本,但最常用的形式通常有五个参数,其中最后一个参数控制连接类型。

connect 函数的基本参数

最常见的 connect 函数原型(或者其等价形式)可以理解为如下结构:

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,const QObject *receiver, const char *member,Qt::ConnectionType type = Qt::AutoConnection);

或者在 Qt5 之后更推荐的函数指针版本:

QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal,const QObject *receiver, PointerToMemberFunction method,Qt::ConnectionType type = Qt::AutoConnection);

让我们分解一下这些参数:

  1. const QObject *sender
    • 发送者对象:这是发出信号的对象。它必须是一个继承自 QObject 的实例。当这个对象的某个特定事件发生时,它会发射一个信号。
  2. const char *signalPointerToMemberFunction signal
    • 信号:这是发送者对象要发出的信号。
      • 在旧的 Qt4 风格和兼容 Qt4 的 Qt5 字符串连接方式中,它是一个字符串,格式为 SIGNAL(signalName(paramType1, paramType2,...))
      • 在 Qt5 引入的函数指针连接方式中,它直接是一个指向信号成员函数的指针,例如 &SenderClass::signalName推荐使用这种方式,因为它能在编译时进行类型检查,减少错误。
  3. const QObject *receiver
    • 接收者对象:这是包含槽函数并响应信号的对象。它也必须是一个继承自 QObject 的实例。
  4. const char *memberPointerToMemberFunction method
    • :这是接收者对象中将要执行的槽函数。
      • 在旧的 Qt4 风格和兼容 Qt4 的 Qt5 字符串连接方式中,它是一个字符串,格式为 SLOT(slotName(paramType1, paramType2,...))
      • 在 Qt5 引入的函数指针连接方式中,它直接是一个指向槽成员函数的指针,例如 &ReceiverClass::slotName。同样,推荐使用这种方式。槽可以是任何被标记为 slots 的成员函数,也可以是普通成员函数、Lambda 表达式或全局函数。
  5. Qt::ConnectionType type = Qt::AutoConnection
    • 连接类型:这是最后一个可选参数,它决定了信号发射时,槽函数是如何被调用的。如果省略,默认值是 Qt::AutoConnection。这是理解信号与槽行为的关键所在,特别是涉及到多线程时。

最后一个参数:Qt::ConnectionType 的三种选择

Qt::ConnectionType 是一个枚举,它定义了信号和槽之间连接的行为。主要的几种类型对于理解 Qt 的多线程编程至关重要。这里我们重点讲解最常用的三种:

1. Qt::AutoConnection (默认值)

  • 含义:这是 connect 函数的默认行为。Qt 会根据发送者和接收者是否在同一个线程中来自动选择连接类型。
  • 行为
    • 如果 发送者和接收者在同一个线程:使用 Qt::DirectConnection。槽函数会立即被调用。
    • 如果 发送者和接收者在不同的线程:使用 Qt::QueuedConnection。信号会被放入接收者所在线程的事件队列中,稍后由该线程的事件循环处理。
  • 优点:方便,省去了手动判断线程的麻烦。
  • 缺点:在某些复杂的线程交互场景下,需要明确指定连接类型以避免潜在的竞态条件或死锁。

2. Qt::DirectConnection

  • 含义:槽函数会在信号被发射时立即调用,就像一个普通的函数调用一样。
  • 行为
    • 槽函数在发出信号的线程中执行。
    • 信号发射后,槽函数立即被调用,然后信号发射器才返回。
    • 参数通过值传递。
  • 优点:实时性高,没有延迟。
  • 缺点
    • 如果在不同线程之间使用 DirectConnection,槽函数仍然在发送者线程中执行。这可能导致线程安全问题,因为槽函数可能会访问接收者对象的数据,而接收者对象属于另一个线程。
    • 可能导致死锁:如果槽函数阻塞了发送者线程,而发送者线程又等待接收者线程的某个操作,就可能出现死锁。
  • 适用场景:主要用于同线程内的对象通信,或者在非常明确知道不会引起线程安全问题的跨线程通信(不推荐)。

3. Qt::QueuedConnection

  • 含义:信号的发射和槽函数的调用是异步的。信号被发送后,不会立即调用槽,而是将一个事件放入接收者对象所在线程的事件队列中。当接收者线程的事件循环处理到这个事件时,才会调用槽函数。
  • 行为
    • 槽函数在接收者对象所在的线程中执行。
    • 信号发射后,立即返回。槽函数会在事件循环空闲时才被调用。
    • 参数通过值传递(Qt 会在内部复制这些参数)。
  • 优点
    • 线程安全:这是在不同线程之间安全通信的主要方式。槽函数在自己的线程中执行,可以安全地访问其线程局部数据。
    • 避免死锁:由于是异步调用,不会阻塞发送者线程。
  • 缺点
    • 存在一定的延迟,因为槽函数需要等待事件循环调度。
    • 参数需要可复制,因为 Qt 会在内部复制参数。
  • 适用场景主要用于跨线程通信,确保槽函数在正确的线程上下文中执行,从而保证线程安全。

总结

理解 connect 函数的参数,特别是 Qt::ConnectionType,对于编写稳定、高效且线程安全的 Qt 应用程序至关重要。在多线程环境中,Qt::QueuedConnection 是跨线程通信的首选,而 Qt::AutoConnection 在大多数情况下可以正常工作,但明确指定连接类型能让代码意图更清晰。


聊一聊我们的QTextStream和QDataStream

QTextStreamQDataStream 都是 Qt 框架中用于数据I/O的类,但它们处理数据的格式和目的截然不同。理解它们的区别对于正确选择数据存储和传输方式至关重要。

QTextStream:文本流


QTextStream 用于处理文本数据。它能方便地读写人类可读的文本,例如字符串、数字(转换为字符串形式)、布尔值等。QTextStream 知道如何处理各种文本编码(如UTF-8, Latin-1等),并且支持流操作符 <<>>,使其用法与 C++ 的 std::coutstd::cin 类似。

主要特点:

  • 人类可读: 输出是文本格式,可以用文本编辑器打开和阅读。
  • 编码支持: 可以指定或自动检测文本编码。
  • 格式化: 支持各种文本格式化选项,如数字的精度、对齐方式等。
  • 平台独立性(文本层面): 只要编码一致,文本文件可以在不同系统上阅读,但其内部数字表示在读取时会转换为字符串,失去原始二进制精度。

适用场景:

  • 保存配置文件 (.ini, .conf)。
  • 生成日志文件 (.log)。
  • 创建 CSV、XML、JSON 等文本格式的数据文件(虽然 Qt 提供了专门的 JSON/XML 类,但 QTextStream 也能处理)。
  • 与用户进行基于文本的I/O。
QTextStream Demo

这个例子演示了如何使用 QTextStream 将一些文本和数字写入文件,然后再从文件中读取回来。

C++

#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QDebug> // 用于输出调试信息int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);QString fileName = "mytextdata.txt";// --- 写入数据到文件 ---QFile outFile(fileName);if (outFile.open(QIODevice::WriteOnly | QIODevice::Text)) {QTextStream out(&outFile);out.setCodec("UTF-8"); // 设置编码,确保中文等字符正确写入out << "Hello, QTextStream!" << endl;out << "This is a line with a number: " << 12345 << endl;out << "And a double: " << 3.14159 << endl;out << "中文文本示例。" << endl;outFile.close();qDebug() << "Text data written to" << fileName;} else {qWarning() << "Could not open file for writing:" << outFile.errorString();}// --- 从文件读取数据 ---QFile inFile(fileName);if (inFile.open(QIODevice::ReadOnly | QIODevice::Text)) {QTextStream in(&inFile);in.setCodec("UTF-8"); // 读取时也要设置相同的编码qDebug() << "\n--- Reading Text Data ---";while (!in.atEnd()) {QString line = in.readLine();qDebug() << line;}inFile.close();} else {qWarning() << "Could not open file for reading:" << inFile.errorString();}return a.exec();
}

运行结果 (mytextdata.txt 文件内容):

Hello, QTextStream!
This is a line with a number: 12345
And a double: 3.14159
中文文本示例。

QDataStream:二进制流


QDataStream 用于处理二进制数据。它能以紧凑的二进制格式读写基本 C++ 类型(如 int, double, bool)、Qt 类型(如 QString, QPoint, QImage)以及自定义的可序列化类型。QDataStream 会保留数据的原始二进制表示,这使得它在不同平台之间进行数据传输时可能需要注意字节序(Endianness)

主要特点:

  • 紧凑高效: 数据以二进制形式存储,占用空间小,读写速度快。
  • 类型保留: 写入什么类型,读取时也必须以相同的类型读取,否则会出错。
  • 平台相关性(字节序): 默认情况下,它使用本地系统的字节序。如果需要在不同字节序的机器之间交换数据,需要使用 setByteOrder() 设置统一的字节序(通常是 Qt::BigEndian)。
  • 版本控制: 支持版本号机制,用于在数据格式变化时保持兼容性。

适用场景:

  • 保存程序的内部状态或对象(序列化)。
  • 在网络中传输数据包。
  • 存储对性能和空间要求较高的非人类可读数据。
  • 需要在不同程序之间共享二进制数据。
QDataStream Demo

这个例子展示了如何使用 QDataStream 将一个整数、一个浮点数和一个字符串写入文件,然后以相同的顺序和类型从文件中读回。

C++

#include <QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug> // 用于输出调试信息int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);QString fileName = "mybinarydata.dat";// --- 写入数据到文件 ---QFile outFile(fileName);if (outFile.open(QIODevice::WriteOnly)) { // 注意这里没有 QIODevice::TextQDataStream out(&outFile);out.setVersion(QDataStream::Qt_6_0); // 推荐设置版本,以保证兼容性out.setByteOrder(QDataStream::LittleEndian); // 明确字节序,增强跨平台兼容性qint32 myInt = 42;double myDouble = 123.456;QString myString = "Hello, QDataStream! 中文示例";out << myInt << myDouble << myString;outFile.close();qDebug() << "Binary data written to" << fileName;} else {qWarning() << "Could not open file for writing:" << outFile.errorString();}// --- 从文件读取数据 ---QFile inFile(fileName);if (inFile.open(QIODevice::ReadOnly)) { // 注意这里没有 QIODevice::TextQDataStream in(&inFile);in.setVersion(QDataStream::Qt_6_0); // 读取时也要设置相同的版本in.setByteOrder(QDataStream::LittleEndian); // 读取时也要设置相同的字节序qint32 readInt;double readDouble;QString readString;in >> readInt >> readDouble >> readString;inFile.close();qDebug() << "\n--- Reading Binary Data ---";qDebug() << "Read Int:" << readInt;qDebug() << "Read Double:" << readDouble;qDebug() << "Read String:" << readString;} else {qWarning() << "Could not open file for reading:" << inFile.errorString();}return a.exec();
}

运行结果:

Binary data written to "mybinarydata.dat"--- Reading Binary Data ---
Read Int: 42
Read Double: 123.456
Read String: "Hello, QDataStream! 中文示例"

mybinarydata.dat 文件内容 (用文本编辑器打开会是乱码,因为是二进制数据):

Qt"`  @? Hello, QDataStream! 中文示例

(以上内容是部分二进制数据被文本编辑器解释成的乱码,实际是紧凑的二进制表示。)

总结区别

特性QTextStreamQDataStream
数据格式文本(人类可读)二进制(机器可读,紧凑)
适用场景配置文件、日志、CSV/XML/JSON等序列化对象、网络传输、性能敏感的私有数据
易读性高,用文本编辑器可直接查看低,用文本编辑器打开是乱码
性能/空间相对较低效,占用空间较大高效,占用空间小
类型处理数字转换为字符串,需要解析保留原始数据类型,直接读写
编码支持多种文本编码 (UTF-8, Latin-1等)无关编码,直接处理字节
跨平台文本内容层面独立;数字存储为字符串,无字节序问题需要注意字节序版本以确保跨平台兼容性

选择 QTextStream 还是 QDataStream 取决于你的具体需求:如果需要人类可读性易于编辑,请选择 QTextStream;如果需要高效存储精确的数据类型保留以及网络传输,则选择 QDataStream。在处理 QDataStream 时,请务必注意版本控制字节序,以确保跨平台和未来兼容性。


http://www.lryc.cn/news/587812.html

相关文章:

  • Sharding-Sphere学习专题(四)广播表和绑定表、分片审计
  • 胡志明证券交易所新一代交易系统解决方案——基于美联储利率决议背景下的越南跨境金融基础设施升
  • 学习C++、QT---25(QT中实现QCombobox库的介绍和用QCombobox设置编码和使用编码的讲解)
  • 2025js——面试题(8)-http
  • 第二章 基于新版Onenet搭建云服务(stm32物联网)
  • 【leetcode】326. 3的幂
  • 对偶原理与蕴含定理
  • SSE(Server-Sent Events)和 MQTT(Message Queuing Telemetry Transport)
  • 【工具】AndroidStudio修改中文语言汉化
  • 【2025/07/14】GitHub 今日热门项目
  • 直播推流技术底层逻辑详解与私有化实现方案-以rmtp rtc hls为例-优雅草卓伊凡
  • QML 常用控件(二)
  • vue中配置Eslint的步骤
  • Why C# and .NET are still relevant in 2025
  • lightgbm算法学习
  • Python----NLP自然语言处理(中文分词器--jieba分词器)
  • 《大数据技术原理与应用》实验报告一 熟悉常用的Linux操作和Hadoop操作
  • .NET控制台应用程序中防止程序立即退出
  • 2025年大数据、建模与智能计算国际会议(ICBDMIC 2025)
  • spring-ai-alibaba 接入Tushare查询股票行情
  • 【C++进阶】---- 多态
  • SpringBoot3整合“Spring Security+JWT”快速实现demo示例与Apifox测试
  • 鸿蒙开发NDK之---- 如何将ArkTs的类型转化成C++对应的类型(基础类型,包含部分代码解释)
  • 系统化构建产品开发体系
  • androidstudio 高低版本兼容
  • 机构参与度及其Python数据获取示例
  • 迁移学习:知识复用的智能迁移引擎 | 从理论到实践的跨域赋能范式
  • 【Canvas与五星】六种五星画法
  • MIPI DSI (一) MIPI DSI 联盟概述
  • 【leetcode】231. 2的幂