QT必备知识

7 篇文章 1 订阅

请添加图片描述


参考:深入理解Qt

事件相关

Qt::WA_TransparentForMouseEvents 设置鼠标事件透传到底层子孙,如果在当前类的构造函数中设置了此值,那么当前类对象就会收不到mousePressEvent等消息处理函数。

QT信号槽机制实现原理

QT信号槽机制是QT中一个非常重要的特性,它是一种对象间通信的机制,允许一个对象(信号源)在某些条件下发出一个信号,而其他对象(槽)则在收到该信号时做出响应。

信号槽机制的实现原理如下:

定义信号和槽函数:在一个对象中定义一个信号,并在其他对象中定义一个槽函数。信号和槽函数都是C++函数,它们遵循特定的命名规则。信号函数通常是在需要发出信号的类中定义,而槽函数通常是在需要处理信号的类中定义。

连接信号和槽:当一个信号被发出时,需要将该信号连接到一个或多个槽函数上。这可以通过使用QObject::connect()函数来实现。这个函数接受三个参数:信号源、信号和槽函数。一旦信号和槽函数被连接,当信号源发出信号时,与该信号连接的所有槽函数都会被自动调用。

发射信号:当信号源需要发出信号时,可以使用emit关键字来调用信号函数。当信号函数被调用时,与该信号连接的所有槽函数都会被自动调用。

处理信号:当槽函数被调用时,它可以访问信号参数,并对信号做出响应。槽函数可以是任意C++函数,它们可以执行任意操作,包括修改信号源的状态、显示消息等。

在信号槽机制中,信号是Qt框架提供的一种特殊的函数,它不返回任何值,但可以有零个或多个参数。槽函数是普通的C++函数,与任何其他函数一样,它们接受参数并返回值。信号槽机制的关键是使用QObject::connect()函数来连接信号和槽函数,这样一旦信号被发出,所有与之连接的槽函数都会被自动调用,从而实现了对象间的通信。

深入分析QT信号槽机制

在Qt中,信号槽机制是通过元对象系统(Meta-Object System)来实现的。元对象系统是一个运行时类型信息(RTTI)机制,用于在运行时查找和操作对象的属性、信号和槽等信息。每个QObject及其派生类都具有一个元对象(meta-object),该元对象在运行时创建,它包含了类的名称、父类的名称、对象的属性、信号和槽等信息。

当QObject及其派生类中定义了信号和槽函数时,Qt会使用元对象系统来注册这些信号和槽函数,并为它们生成元对象代码。这些元对象代码在应用程序启动时被执行,它们将信号和槽函数注册到元对象系统中,从而使它们能够在运行时被动态地查找和连接。

在运行时,当信号被发出时,元对象系统会根据信号源的元对象来查找与该信号相匹配的槽函数。如果找到了匹配的槽函数,元对象系统会调用该槽函数,并将信号参数传递给它。如果没有找到匹配的槽函数,那么该信号就被忽略。

在连接信号和槽时,QObject::connect()函数的内部实现使用了Qt的元对象系统。当调用该函数时,Qt会在元对象系统中查找信号和槽函数,并将它们连接起来。在连接过程中,Qt还会检查信号和槽函数的参数类型和数量是否匹配,以及它们是否都是可调用的(即可被调用的对象和方法是否存在)。

值得注意的是,由于元对象系统的存在,Qt信号槽机制可以在编译期外连接,这使得Qt信号槽机制成为了Qt框架的核心功能之一。同时,这也让开发者能够更加灵活地编写代码,将信号和槽函数连接在不同的对象之间,从而实现对象之间的通信。

如果信号和槽函数处在不同的线程中,Qt信号槽机制会根据以下情况进行不同的处理:

如果信号和槽函数处在同一个线程中,那么它们的连接和调用与单线程模式下是一样的。

如果信号和槽函数处在不同的线程中,并且它们的父对象是同一个,那么它们的连接和调用与单线程模式下也是一样的。因为它们的父对象是同一个,所以它们会被同一个线程管理,这样就避免了线程安全问题。

如果信号和槽函数处在不同的线程中,并且它们的父对象不同,那么Qt会自动将信号的参数复制到槽函数所在的线程中,并在槽函数所在的线程中执行槽函数。这个过程是通过Qt提供的事件循环机制实现的。

具体来说,当信号发出时,Qt会将信号加入到目标对象所在的线程的事件队列中。当目标对象所在的线程开始处理事件时,它会检查事件队列中是否有新的信号需要处理,如果有,那么它就会执行与该信号相关联的槽函数。在执行槽函数之前,Qt会先将信号的参数复制到槽函数所在的线程中,这样就保证了线程安全性。

signals

signals:
    void testSignal(int a);

中的signals是一个宏,定义如下:

#ifdef signals
#  define signals public __attribute__((annotate("qt_signal")))
#endif

点击QT窗口到响应的过程

当您点击Qt窗口时,Qt会在您点击的位置生成一个事件并将其发送到事件循环。事件循环是Qt应用程序中负责处理所有事件的主循环。它等待事件,并在接收到事件时对其进行处理。

当事件循环接收到鼠标点击事件时,它会检查该事件的类型,以确定如何处理该事件。如果该事件是鼠标单击事件,则事件循环会检查该单击是否发生在一个QWidget上(例如按钮)。

如果该单击发生在QWidget上,则事件循环会调用QWidget的事件处理函数,该函数会对该事件进行处理。例如,如果该QWidget是一个按钮,则该函数可能会触发按钮的单击信号,从而触发该信号的相关槽函数。槽函数是用户定义的函数,其中包含用户希望在该事件发生时执行的代码。

注册自定义类型

Qt信号槽或者QML中使用自定义类型,需要注意使用qRegisterMetaType对自定义类型进行注册
在当前类的顶部包含:#include <QMetaType>,构造函数中加入代码:qRegisterMetaType<MyClass>("Myclass");

moc实现原理

Q_OBJECT的作用是什么,内部实现了些什么

Q_OBJECT 是 Qt 框架中的一个宏定义,用于在类的声明中标记该类需要使用 Qt 的元对象系统(Meta-Object System)。使用 Q_OBJECT 宏定义后,编译器会在编译期自动生成元对象代码,包括信号(signal)和槽(slot)的注册、元对象信息的注册等等。

具体来说,使用 Q_OBJECT 宏定义后,编译器会为该类生成一个 QMetaObject 对象,该对象包含了该类的元对象信息,包括类名、信号和槽的名称、参数类型等等。这些信息可以通过 QObject::metaObject() 函数获取到。

此外,使用 Q_OBJECT 宏定义后,还可以在该类中使用信号和槽,使用 emit 关键字来发射信号,使用 connect 函数将信号和槽连接起来。这些功能都是通过 Qt 的元对象系统实现的。

需要注意的是,使用 Q_OBJECT 宏定义的类必须直接或间接继承自 QObject 类。

  • 实现原理
    Q_OBJECT 宏定义会为该类自动添加一些成员变量和成员函数,用于支持 Qt 的元对象系统。
  • 1、 QObject 类的虚函数 metaObject(),它返回一个描述该对象的元对象。
  • 2、QMetaObject 类型的静态变量,用于存储该对象的元对象。

QObject

完整看一遍:线程和 QObject

QObject与多线程

QObject的依附线程
  • QObject::thread()可以查询一个QObject的线程依附性。每一个QObject对象或者子对象,都拥有一个记录所依附线程的成员数据:QThreadData threadData,记录了拥有这个QObject对象的线程ID号。
  • 一个QObject的所依附的线程(thread affinity)是指它所在的那个线程,即QObject对象创建的那个线程。
  • 线程的事件循环会给依附在这个线程上的所有QOjbect对象派发事件。
  • Qt 要求QObject的所有子对象都必须和其父对象在同一线程。
    因此不能在QThread中以这个QThread本身作为父对象创建对象,例如:
class Thread : public QThread {
    void run() {
        QObject *obj = new QObject(this); // 错误!
    }
};

this对象是属于创建这个线程的线程,而obj(设置this为其父)是属于这个线程的,那就跟它的父this不是归属于同一个线程,这是不允许的。

QObject是否是线程安全的

QObject及其所有子类都不是线程安全的(但都是可重入的)。因此,你不能有两个线程同时访问一个QObject对象,除非这个对象的内部数据都已经很好地序列化(例如为每个数据访问加锁)。

QThread

这里有两个概念:

  • QThread对象所在的线程 —— 创建QThread对象所在的那个线程,而不是创建的那个子线程
  • QThread对象所管理的线程 —— 真正创建的那个子线程
  • 线程的事件循环用于为线程中的所有QObjects对象分发事件;默认情况下,这些对象包括线程中创建的所有对象,或者是在别处创建完成后被移动到该线程的对象。
QObject的线程依附性是否可以改变

调用QObject::moveToThread()函数。该函数会改变一个对象及其所有子对象的线程依附性。

  • 由于QObject本身是线程不安全的,因此moveToThread接口的调用必须在QObject对象所在的线程内调用。
如何安全的在另外一个线程中调用QObject对象的接口

QObject被设计成在一个单线程中创建与使用,因此,在一个线程中创建一个对象,而在另外的线程中调用它的函数,这样的行为不能保证工作良好。

  • 使用信号槽的队列连接或者QT的反射系统提供的QMetaObject::invokeMethed的队列连接调用。这要求接口必须是内省的,也就是说这个函数要么是一个槽函数,要么标记有Q_INVOKABLE宏。
  • 将事件提交到接收对象所在线程的事件循环;当事件发出时,响应函数就会被调用。

事件

重点阅读:深入理解Qt——事件循环
QT 事件的传递先后顺序

QEventLoop::exec

用法1:

void widget::on_pushButton_clicked()
{
........ 需要等待100毫秒的时间.等待某个条件成立
    //事件循环: 在指定时间后执行某件事. 不卡UI界面
    QEventLoop loop;
    QTimer::singleShot(100, &loop, SLOT(quit()));
    loop.exec();
.........
继续执行代码.
}

用法2:

QEventLoop loop;
void widget::on_pushButton1_clicked()
{
........ 需要等待某个条件成立
    loop.exec();
.........
继续执行代码.
}

//槽函数: 等待的条件满足时进入
void widget::on_timeclicked()
{
..........
loop.exit();  //退出事件循环
}
为何在通过QEventLoop::exec()阻塞程序执行,程序却不会卡死

因为QEventLoop::exec()开启了一个新的事件循环来分发事件,而且相同线程上的所有事件循环采用同一个事件调度器。

事件循环

主事件循环
int QCoreApplication::exec()

主事件循环接收来自系统的事件,并转换为QT的事件消息,并把这些事件分发到对应的应用程序窗口上。

notify

bool QCoreApplication::notify(QObject *receiver, QEvent *event)
  • 把事件发送到receiver的事件处理函数{receiver}->event(event)中
  • 如果接收者receiver对event事件不感兴趣,事件则会传递给receiver的父对象,直到顶级对象的事件处理函数中。

5种处理事件的方法:

  • 1、重写QObject的事件处理函数mousePressEvent
  • 2、重写QCoreApplication::notify 拥有完整的控制
  • 3、给QCoreApplication::instance()安装一个事件过滤器,因此事件过滤器里能处理所有控件的所有事件,控制强度跟notify一样。
  • 4、重写QObject::event()
  • 5、给一个QObject对象安装一个事件过滤器,这个事件过滤器则会处理这个QObject对象的所有事件。

sendEvent

bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
  • 使用notify函数,直接把事件给到接受者
  • event最好是栈空间,sendEvent不会删除event对象。

postEvent

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)

把事件event投递到对象receiver所在事件队列中,并立即返回。

  • 线程安全的QCoreApplication::postEvent()函数向一个对象发送事件。它将把事件加入到对象所在的线程的事件队列中,因此,如果这个线程没有运行事件循环,这个事件也不会被派发。
  • event必须在堆上分配,因为postEvent会获取event的所有权,并把原始的event删除。
  • 当控制返回到主事件循环,那么所有存储在事件队列中的一个个事件将会被调用notify函数分发出去。
  • 事件队列中的事件按事件优先级降序排序,即高优先级的事件在队头。
  • 这个接口是线程安全的

deleteLater

对于deleteLater的用法,官方描述如下:

    The object will be deleted when control returns to the event
    loop. If the event loop is not running when this function is
    called (e.g. deleteLater() is called on an object before
    QCoreApplication::exec()), the object will be deleted once the
    event loop is started. If deleteLater() is called after the main event loop
    has stopped, the object will not be deleted.
    Since Qt 4.8, if deleteLater() is called on an object that lives in a
    thread with no running event loop, the object will be destroyed when the
    thread finishes.

实现如下:

void QObject::deleteLater()
{
    QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
}

它是线程安全的删除QObject对象的方法。

  • 分析下deleteLater调用后的过程
    Aobj->deleteLater()相当于QCoreApplication::postEvent(Aobj, new QDeferredDeleteEvent());,把一个QDeferredDeleteEvent事件投递到了Aobj所依附的线程的事件队列中,等Aobj所依附的线程的事件循环获得控制权时,删除Aobj对象。

Qobj->deleteLater()调用后,Qobj这个对象真正被执行删除的时候是在(当前这个线程?)重新回到事件循环的时候处理的。如果在事件循环未启动前就调用deleteLater,那么这个对象会在事件循环一启动就会被删除。如果在事件循环结束后,再调用deleteLater,那么这个对象将不会被删除。
如果在一个子线程中,这个子线程没有运行事件循环,那么这个对象的删除,将在子线程结束时删除。而对于整个程序而言,主线程都会有事件循环(因为,return QApplication::exec()),所以主线程中删除对象不会存在这个情形。

  • 一个对象调用deleteLater后,再调用它的接口,是否会有问题
    不会有问题。不会立刻删除对象,直到进入事件循环

模型/视图机制

MVC

模型(Model): 模型负责管理应用程序的数据和业务逻辑。它独立于用户界面,提供了数据的获取、存储、修改等功能。模型通常是应用程序的核心部分,通过提供接口供视图和控制器访问和操作数据。

视图(View): 视图负责显示模型中的数据以及用户界面的外观和布局。它根据模型的数据进行更新,用户通过视图来看到数据的呈现。视图不处理业务逻辑,只负责显示和用户交互。

控制器(Controller): 控制器是用户界面和模型之间的中介,处理用户输入并更新模型和视图。它将用户的操作转化为模型的更新和视图的刷新。控制器使得模型和视图可以解耦,从而实现更好的代码组织和维护。

MVC示例

#include <QtWidgets>

// 模型类
class Model : public QObject {
    Q_OBJECT
public:
    explicit Model(QObject *parent = nullptr) : QObject(parent) {
        data = "Hello, MVC!";
    }

    QString getData() const {
        return data;
    }

private:
    QString data;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    Model model;

    // 视图类
    QLabel label;
    label.show();

    // 控制器
    QObject::connect(&label, &QLabel::linkActivated, [&model, &label]() {
        label.setText(model.getData());
    });

    label.setText("<a href='#'>Click Me</a>");

    return app.exec();
}

#include "main.moc"

QT 中的模型/视图机制是一种用于管理数据的框架,可以将数据和其显示方式分离,并提供一种通用的接口来访问和操作数据。在 QT 中,模型/视图机制通常使用 QAbstractItemModel、QAbstractListModel、QAbstractTableModel 等类来实现。

模型负责提供数据,视图负责将数据显示在界面上,并允许用户进行操作。视图通常包括 QListView、QTableView、QTreeView 等类,用于显示不同类型的数据。当模型数据发生变化时,模型会自动发送信号给视图,并通知视图进行更新。同时,视图也可以通过发送信号给模型来请求数据,例如获取某一行、某一列的数据等。

QT 中的模型/视图机制支持多种不同的数据源,例如内存中的数据、数据库中的数据、XML 文件中的数据等。此外,QT 中还提供了一些常用的模型实现,例如 QDirModel、QFileSystemModel、QStandardItemModel 等,用于快速创建常见的数据模型。

QFrame与QWidget的区别

QFrame 和 QWidget 都是 Qt 中的 GUI 组件,但是它们有一些区别:

继承关系:QFrame 继承自 QWidget,所以 QFrame 具有 QWidget 的所有功能。

功能:QFrame 提供了一个简单的框架,可以作为其他控件的容器。它还可以用来绘制简单的图形,如线条。QWidget 没有这样的功能,但是提供了基础的 GUI 组件功能,如设置尺寸和位置等。

外观:QFrame 可以有边框和背景颜色,因此外观更加丰富。QWidget 只有背景颜色,没有边框。

通常,当需要一个简单的框架时,使用 QFrame,当需要基础的 GUI 组件功能时,使用 QWidget。

信号槽

参考:https://www.kancloud.cn/kancloud/qt-study-road-2/99456

信号重载了,如何确定连接哪个信号?

答,采用函数指针确定连接哪个信号。

槽函数参数、信号的参数

槽函数的参数可以少于信号的参数。

  • 槽函数本身参数比信号的少
  • 槽函数参数带有默认参数

槽函数的参数是否可以比信号的参数多?

也可以。唯一的情况就是槽函数参数带有默认参数,除去默认参数外,槽函数的参数必须小于等于信号的参数。

什么是 QT?QT 的优势是什么?
QT 的事件机制是什么?什么是事件循环?
QT 中的信号和槽是什么?如何使用信号和槽连接不同的对象?
QT 中的多线程机制是什么?如何在 QT 中使用多线程?
QT 中常用的布局管理器有哪些?如何使用布局管理器来布置窗口控件?
QT 中的界面控件有哪些?如何使用界面控件来实现特定的功能?
QT 中如何实现自定义控件?
QT 中的文件系统和网络编程是如何实现的?如何使用 QT 进行文件读写和网络通信?
QT 中如何进行国际化和本地化?
QT 中如何进行调试和性能优化?
当然,除了这些问题,面试官可能还会问到一些其他的 QT 相关问题,比如 QT 中的绘图机制、动画效果、插件机制等等。建议在面试前认真复习 QT 的相关知识点,并做好充分的准备。

隐式共享

Graphics View Framework

在这里插入图片描述


请添加图片描述

  • 0
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值