一、Qt信号槽机制的优缺点
问题:
为什么Qt使用信号与槽机制而不是传统的回调函数机制进行对象间的通信呢?
回调函数的本质是“你想让别人的代码执行你的代码,而别人的代码你又不能动”这种需求下产生的。
回调函数是函数指针的一种用法,如果多个类都关注某个类的状态变化,此时需要维护一个列表,以存放多个回调函数的地址。对于每一个被关注的类,都需要做类似的工作,因此这种做法效率低,不灵活。
解决办法:
Qt使用信号与槽机制来解决这个问题,程序员只需要指定一个类含有哪些信号函数、哪些槽函数,Qt会处理信号函数和槽函数之间的绑定。当信号函数被调用时,Qt会找到并执行与其绑定的槽函数。允许一个信号函数和多个槽函数绑定,Qt会依次找到并执行与一个信号函数绑定的所有槽函数,这种处理方式更灵活。
优点:
- Qt信号与槽机制降低了Qt对象的耦合度。各对象的代码可以被独立地开发、测试,也更容易被服用。
二、多线程环境 Qt中的信号槽分别在什么线程中执行, 如何控制?
通过connect函数的第五个参数connectType来控制。
connect用于连接qt的信号和槽,在qt编程过程中不可或缺。它其实有第五个参数,只是一般使用默认值,在满足某些特殊需求的时候可能需要手动设置。
Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。
- 如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。
- 如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成崩溃。
Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
三、Qt 信号槽机制
自定义信号槽注意事项:
- 发送者和接收者都需要是QObject的子类(槽函数是全局函数、Lambda 表达式等无需接收者的时候除外)
- 使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
- 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
- 使用 emit 在恰当的位置发送信号;
- 使用QObject::connect()函数连接信号和槽;
- 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。
信号槽的多种用法:
- 一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
- 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。
- 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
- 槽可以被取消链接
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
- 使用Lambda 表达式
在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
四、QT事件机制中事件过滤的级别及其描述下?
根据对Qt事件机制的分析,我们可以得到5种级别的事件过滤,处理办法。
以功能从弱到强,排列如下:
- 重载特定事件处理函数
最常见的事件处理办法就是重载像mousePressEvent()
,keyPressEvent()
,paintEvent()
这样的特定事件处理函数.
- 重载
event()
函数
通过重载event()
函数,我们可以在事件被特定的事件处理函数处理之前(像keyPressEvent()
)处理它。
比如,当我们想改变tab键的默认动作时,一般要重载这个函数。
当我们重载event()
函数时,需要调用父类的event()
函数来处理我们不需要处理或是不清楚如何处理的事件。
- 在Qt对象上安装事件过滤器
安装事件过滤器有两个步骤:(假设要用A来监视过滤B的事件)
首先调用B的installEventFilter(const QOject *obj)
,以A的指针作为参数。这样所有发往B的事件都将先由A的eventFilter()
处理。
然后,A要重载QObject::eventFilter()
函数,在eventFilter()
中书写对事件进行处理的代码.
- 给QAppliction对象安装事件过滤器
一旦我们给qApp(每个程序中唯一的QApplication
对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个eventFilter()
。在debug的时候,这个办法就非常有用,也常常被用来处理失效了的widget的鼠标事件,通常这些事件会被QApplication::notify()
丢掉。(在QApplication::notify()
中,是先调用qApp的过滤器,再对事件进行分析,以决定是否合并或丢弃)
- 继承QApplication类,并重载
notify()
函数
Qt是用QApplication::notify()
函数来分发事件的。想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法。通常来说事件过滤器更好用一些,因为不需要去继承QApplication类。而且可以给QApplication
对象安装任意个数的事件。
五、QT5的信号槽与QT4相比有什么改进?
- 编译期:检查信号与槽是否存在,参数类型检查,
Q_OBJECT
是否存在 - 信号可以和普通的函数、类的普通成员函数、lambda函数连接(而不再局限于信号函数和槽函数)
- 参数可以是
typedef
的或使用不同的namespace specifier
- 可以允许一些自动的类型转换(即信号和槽参数类型不必完全匹配)
六、Qt的布局
七、对QObject的了解
7.1 对象树
Qt库中很多都以QObject作为它们的基类。QObject的对象总是以树状结构组织自己。当我们创建一个QObject对象时,可以指定其父对象,新创建的对象将被加入到父对象的子对象列表中,
- 当父对象被析构时,这个列表中的所有子对象会被析构。
- 当某个QObject对象被析构时,它会将自己从父对象的列表中删除,以避免父对象被析构时,再次析构自己
由于QObject对象具有析构功能,所以当我们使用new操作符在堆中创建QObject对象时,并不需要使用 delete 操作符析构它们。当一个QObject对象被析构时,该对象会被自动析构。甚至,当出于一些特殊原因需要显式地析构某个QObject对象时,其父对象也会在子对象列表中将其删除,不会导致该父对象将来被析构时,再次析构该子对象。
当我们以局部变量方式在栈中定义QObject对象时,应该先定义父对象,再定义子对象。
7.2 元对象系统
- Qt元对象系统为QObject派生类的对象为QObject派生类的对象提供非常详细的运行时类型信息以及数据成员的当前值信息。
即:给定一个QObject派生类对象,应用程序在运行阶段可以获取该对象所属类名称、父类名称、具有的成员函数、枚举类型、数据成员,还可根据数据成员的名称得到对应的值。
- Qt使用预处理工具moc来实现元对象系统。在定义QObject派生类时,调用宏Q_OBJECT定义一些成员函数,moc分析该头文件,生成一些源文件,这些源文件含有实现这些成员函数代码,应用程序调用这些成员函数获取QObject派生类对象的详细信息。
八、Qt的模型视图
九、Qt的线程知识
十、线程池
十一、信号和槽的定义
信号函数的语法约束:
- 函数返回值是void,因为出发信号函数的目的是执行与其绑定的槽函数,无需信号函数返回任何值
- 只能声明、不能实现信号函数,因为Qt的moc会实现它,无需程序员关心
- 调用信号函数可以使用 emit关键字。(新版本的Qt不需要emit关键字,Qt5.7不需要)
- 信号函数被moc自动设置为protected,因而只有包含一个信号函数那个类及其派生类能够出发该信号函数
- 信号函数的传参个数、类型由程序员自行设定。这些参数的职责是封装类的状态信息,并将这些信息传递给槽函数
- 只有QObject及其派生类才可以定义信号函数
槽函数的语法约束:
- 函数返回值是void类型,因为信号与槽机制是单向的:信号函数被触发后,与其绑定的槽函数会被执行,但不要求槽函数返回任何执行结果
- 一个类的槽函数可以如同其他成员函数一样被调用
- 一个类的槽函数可以是
public
,protected
以及private
,这些关键字的含义依旧,也就是说,它们能够控制其他类是否能够以正常的方式调用一个槽函数 - 只有QObject及其派生类才可以定义槽函数