1. QT中connect函数的第五个参数是什么?有什么作用?
在 Qt 中使用 connect 连接信号与槽时,通常有以下几个参数:
- 发送者对象:即发出信号的对象
- 信号:要连接的信号,例如 clicked()
- 接收者对象:即接收信号的对象
- 槽函数:要连接的槽函数,即信号发出时要执行的函数
- 连接类型:可以是 Qt::ConnectionType 的枚举值,如 Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection 等,默认为 Qt::AutoConnection
connect(sender, SIGNAL(clicked()), receiver, SLOT(handleButtonClicked()));
在Qt中,connect函数的第五个参数是connect_mode,它是一个布尔值,用于指定连接模式。
connect_mode参数的作用是决定信号和槽的连接方式。
当connect_mode为false时,信号和槽使用常规的连接方式,即当信号发出时,相应的槽函数将被调用。
当connect_mode为true时,信号和槽使用queued connection模式。在这种模式下,信号将不会立即传递给槽,而是被缓存起来,等待线程调度器将槽函数调度执行。这种模式适用于多线程环境,可以避免不必要的线程间同步和竞争条件。
总结起来,connect函数的第五个参数connect_mode用于指定信号和槽的连接模式,决定信号是否立即传递给槽,以及在多线程环境下的行为。
2. QT信号和槽的机制的优点是什么?
QT信号和槽机制的优点包括:
-
类型安全:信号和槽的签名必须是等同的,即信号的参数类型和参数个数必须与接收该信号的槽的参数类型和参数个数相同。
-
松散耦合:信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无须知道是哪个对象的哪个槽需要接收它发出的信号,而对象的槽也不知道是哪些信号关联了自己。这样,一个对象可以在不等待接收者的响应下继续执行,提高了程序的响应性能。即使关联的对象在运行时被删除,应用程序也不会崩溃。
-
异步通信:信号和槽机制可以实现异步通信,一个对象可以在不等待接收者的响应下继续执行,提高了程序的响应性能。
-
事件驱动:在Qt中的图形界面编程常常与事件处理有关。信号与槽机制使得处理事件变得更加方便,能够轻松地处理按钮点击、鼠标事件等。
虽然信号和槽机制具有许多优点,但也存在一些性能损失。与直接调用非虚函数相比,通过传递一个信号来调用槽函数可能会运行速度更慢。
3. 在QT中,如何使用QSqlDatabase类的transaction()函数来开始一个事务,如何使用commit()函数来提交事务,如何使用rollback()函数来回滚事务
- 代码实例如下
#include <QSqlDatabase>
#include <QSqlQuery>
QSqlDatabase db = QSqlDatabase::database(); // 获取数据库连接
if (db.transaction()) { // 开始事务
QSqlQuery query(db);
// 在这里执行数据库操作,例如插入、更新或删除数据
// 如果所有操作都成功执行,则继续提交事务
if (query.exec("INSERT INTO table_name (column1, column2) VALUES (value1, value2)")) {
// 插入操作成功执行
if (query.exec("UPDATE table_name SET column1 = value1 WHERE column2 = value2")) {
// 更新操作成功执行
// 提交事务
if (db.commit()) {
// 事务提交成功,所有操作永久保存
qDebug() << "Transaction committed successfully";
} else {
// 事务提交失败,回滚所有操作
qDebug() << "Failed to commit transaction";
db.rollback(); // 回滚事务
}
} else {
// 更新操作失败,回滚所有操作
qDebug() << "Failed to execute update query";
db.rollback(); // 回滚事务
}
} else {
// 插入操作失败,回滚所有操作
qDebug() << "Failed to execute insert query";
db.rollback(); // 回滚事务
}
} else {
// 开始事务失败
qDebug() << "Failed to start transaction";
}
在上面的例子中,首先通过QSqlDatabase::database()获取数据库连接。
然后使用transaction()函数开始一个事务。
在事务中,可以执行多个数据库操作,例如插入、更新或删除数据。
如果所有操作都成功执行,则使用commit()函数提交事务,否则使用rollback()函数回滚事务。
在提交或回滚事务之前,可以使用QSqlQuery对象执行具体的数据库操作。
4. QT数据库中的事务 transaction的作用是什么?举例说明
QT数据库中的事务(Transaction)是一组操作的逻辑单元,它要么全部成功地执行,要么全部失败回滚。事务的主要作用是确保数据库的一致性和完整性。
在数据库和软件开发中,事务是不可分割的最小工作单位。它对数据库中一组相关的操作进行处理,要么全部执行成功并永久保存到数据库中,要么如果有任何操作失败,则撤消所有已执行的操作。这样可以保证数据库中的数据始终保持一致性和完整性。
例如,在银行转账操作中,事务可以起到重要作用。假设一个用户要将自己的账户中的资金转移到另一个用户的账户中,这个过程可以通过一个事务来实现。事务将包括两个操作:从一个账户中减去转账金额,然后在另一个账户中增加相同的金额。如果这两个操作都成功执行,则事务可以提交,转账操作永久保存。但是,如果其中一个操作失败,例如因为账户余额不足,则事务将回滚,所有已执行的操作都将被撤销,转账操作不会生效。
在QT中,可以使用QSqlDatabase类的transaction()函数来开始一个事务,然后使用commit()函数来提交事务,或者使用rollback()函数来回滚事务。这样可以确保对数据库的修改要么全部成功,要么全部失败回滚,保持数据库的一致性和完整性。
总之,QT数据库中的事务是一种非常重要的机制,它可以确保数据库的一致性和完整性,保证一组相关的操作要么全部成功执行,要么全部失败回滚。
5.QT中可以用子线程操作UI界面吗
在QT中,子线程无法直接操作UI界面,否则会出现线程冲突等错误。但可以通过以下两种方法实现子线程与UI线程的交互:
使用信号和槽机制:子线程可以发出信号,UI线程中的槽函数可以接收并处理这些信号,从而实现对UI的更新。这是一种安全且常见的方法,可以避免线程冲突。
使用InvokeMethod方法:InvokeMethod可以在指定的QObject对象上调用指定的方法,同时还可以指定调用方法的参数。使用该方法可以在子线程中调用UI线程中的方法,实现对UI的更新。但需要注意的是,使用该方法时需要确保调用的方法不会阻塞子线程,否则会影响程序的性能。
总之,虽然QT的子线程无法直接操作UI界面,但可以通过信号和槽机制或InvokeMethod方法实现子线程与UI线程的交互,完成UI的更新。
6. QT中的事件处理机制的什么?
QT中的事件处理机制是一种基于事件循环的机制,它通过不断地从事件队列中获取并处理事件来更新应用程序的状态。
事件可以来自不同的源,包括用户交互、系统事件、定时器事件等。当这些事件发生时,它们被添加到事件队列中,等待被处理。
QT的事件处理机制主要通过以下几个步骤实现:
事件循环的启动:应用程序启动后,通过调用QCoreApplication::exec()函数进入事件循环。这个函数会一直运行,直到QCoreApplication::exit()函数被调用。
事件的获取和处理:在事件循环中,QT会不断地从事件队列中获取事件,并根据事件的类型调用相应的处理函数。这些处理函数通常是在QT的组件类中定义的,例如QWidget、QPushButton等。
事件的传播:当一个事件发生时,它首先被发送到最顶层的组件,然后按照组件的层次结构向下传播,直到找到处理该事件的组件。如果一个组件不处理该事件,它会将其传递给父组件或其他组件处理。
事件的处理:当一个组件接收到事件时,它会根据事件的类型调用相应的处理函数。这些处理函数可以是虚函数,例如在QWidget类中的mousePressEvent、mouseMoveEvent等,也可以是自定义的函数。如果事件被成功处理,处理函数会返回true;否则返回false。
事件的结束:当一个事件被处理后,QT会将其从事件队列中移除,并继续处理下一个事件。
需要注意的是,QT的事件处理机制是异步的,这意味着当一个事件正在被处理时,其他事件仍然可以被添加到事件队列中并等待被处理。
这种机制使得QT的应用程序能够响应用户的交互和系统的变化,并保持高效的运行。
7.QT setmousetracking的作用
在QT中,setMouseTracking函数用于设置组件是否跟踪鼠标移动事件。如果一个组件启用了鼠标移动跟踪,它会在鼠标移动时接收mouseMoveEvent事件,并能够响应这些事件。默认情况下,QT中的组件都没有启用鼠标移动跟踪,这意味着它们只有在鼠标按钮按下或释放时才会接收鼠标事件。如果你想在鼠标移动时接收鼠标事件,可以使用setMouseTracking(True)方法来启用鼠标移动跟踪。
8.QObject类是如何实现了信号与槽机制的?
QObject类实现信号与槽机制的方式主要依赖于Qt的元对象系统。元对象系统包括三个重要的组成部分:QObject类,它是所有使用元对象系统的基类;Q_OBJECT宏,它使得类可以使用元对象特性;以及moc(meta-object-compiler,元对象编译器),它预处理包含Q_OBJECT宏的类。
首先,QObject类中的connect函数用于建立信号与槽的连接。这个函数接收四个参数:发送对象、信号、接收对象和槽。当信号被发出时,会调用与之连接的槽函数。
在编译过程中,Qt的moc工具会预处理包含Q_OBJECT宏的类。它会展开Q_OBJECT宏,并自动生成一些变量定义、函数声明等代码。这些代码包括将信号与槽作为可回调的函数,并根据id调用这些函数。这些函数及其id的定义在Q_OBJECT展开的函数qt_static_metacall中。
信号的实现由moc完成,它会生成相应的代码来发出信号。而槽函数的实现则需要程序员自己完成。当信号被发出时,会根据连接的槽函数的id调用相应的槽函数。
总的来说,QObject类通过利用元对象系统中的信息,实现了信号与槽机制的建立、发出和调用的整个过程。
9.QT的信号与槽的底层实现是什么?原理是什么?
QT的信号与槽的底层实现是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。信号和槽机制实现的功能其实就是信号函数调用槽函数的效果。
在QT中,每个对象都有一个相应的记录该对象的元对象,元对象类为QMetaObject,记录元对象数据信号与槽的是QMetaData类。QObject类实现了信号与槽机制,它利用元对象记录的信息,实现了信号与槽机制。
信号与槽建立连接的实现是通过QObject类的connect()函数,它的参数包括发送对象、信号、接收对象和槽。连接内部的实现接口是connectInternal()。当信号发生时,会激活操作函数接口QObject::active_signal()。
在实际开发中,可以使用QT提供的信号函数和槽函数,也可以根据需要自定义信号函数和槽函数。QT Creator 提供了很强大的QT GUI开发手册,可以很容易地查到某个控件类中包含哪些信号函数和槽函数。
10.Qt 的优点、缺点
优点:
跨平台,几乎支持所有平台
接口简单,文档详细
开发效率高
缺点:
Qt 作为一个软件平台,比较庞大、臃肿。
11.Qt 的核心机制
元对象系统:
Qt的元对象系统(meta-object)提供了用于内部对象通讯的信号与槽(signals & slots)机制,运行时类型信息,以及动态属性系统(dynamic property system)。 整个元对象系统基于三个东西建立:
QObject类为所有对象提供了一个基类,只要继承此类,那创建出的对象便可以使用元对象系统。
在声明类时,将Q_OBJECT宏放置于类的私有区域就可以在类中使能元对象特性,诸如动态属性,信号,以及槽。一般实际使用中,我们总是把Q_OBJECT宏放置在类声明时的开头位置,除此之外我们的类还需要继承QObject类。
元对象编译器(Meta-Object Compiler,缩写moc),为每个QObject的子类提供必要的代码去实现元对象特性。我们可以认为Qt对C++进行了一些拓展,moc则是负责将这些拓展语法翻译成原生的C++语法,之后交给C++编译器去编译。
12.信号与槽机制原理
信号与槽的具体流程。
moc查找头文件中的signals,slots,标记出信号和槽。
将信号槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引。
当发现有connect连接时,将信号槽的索引信息放到一个map中,彼此配对。
当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数
通过active函数找到在map中找到所有与信号对应的槽索引
根据槽索引找到槽函数,执行槽函数
13.Qt信号槽机制的优势和不足
优点:类型安全,松散耦合。缺点:同回调函数相比,运行速度较慢。
优点:
类型安全:需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,则编译器会报错。
松散耦合:信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是哪个对象的那个槽接收它发出的信号,它只需要在适当的时间发送适当的信号即可,而不需要关心是否被接收和哪个对象接收了。Qt保证适当的槽得到了调用,即使关联的对象在运行时删除,程序也不会崩溃。
灵活性:一个信号可以关联多个槽,多个信号也可以关联同一个槽。
缺点:
速度较慢:与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍左右。
原因:
需要定位接收信号的对象。
安全地遍历所有槽。
编组,解组传递参数。
多线程的时候,信号需要排队等候。(然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损失,对于实时应用程序是可以忽略的。)
14.信号与槽与函数指针的比较
回调函数使用函数指针来实现的,如果多个类都关注一个类的动态变化,这样就会需要写出一个比较长的列表来管理这些类之间的关系。稍微在编码方面不那么灵活,稍显冗余。
QT使用信号与槽来解决这个连接问题,这种方式比较清晰简单一些,一个类只需要清楚自己有几个槽函数有几个信号,然后将信号与槽进行连接,QT会自己处理函数的调用关系。这样在软件设计角度更加的清晰,灵活,不容易出错。
Qt信号与槽机制降低了Qt对象的耦合度。
发信号的对象不需要知道有几个槽函数,也不需要关系是否收到信号,或者谁收到了,谁没收到。
同样的槽函数也不需要知道谁是信号的发出者。信号只需要在合适的时机发出即可,降低了对象之间的耦合度。
15.Qt 的事件过滤器
父窗口类通过重写eventFilter方法来监听子控件的相关事件进行处理。
使用这种方式的好处是不需要通过重写控件的方式获取某些事件,对于安装了事件过滤器的对象,他们所有的事件都会经过这个事件过滤器,所以就可以直接在父窗口中进行监测。
比如某个窗口中有个QLabel对象,我们想要监听他的鼠标点击事件,那我们就需要继承QLabel类,然后重写mousePressEvent事件,如果有其他类型的控件也需要获取某个事件,那是不是都需要继续控件并重写某个事件了,所以我们通过事件过滤器就可以很方便获取某个对象的某个事件。
专门的事件过滤器类,对特定的对象/特定的事件进行处理。
事件过滤器类只需对当前安装的对象进行处理,无需关心其他操作,且一个事件过滤器类可以被多个对象使用,例如Qt文档中的按键过滤示例,KeyPressEater类中的eventFilter过滤了所有的键盘按下事件,只要安装此事件过滤器的控件,都接收不到键盘按键按下的事件,这种就是对某个通用的事件进行过滤,可以进行多次复用。
给QApplication安装事件过滤器,达到全局事件监听的效果。
在notify方法下发事件的时候,QApplication对象可以拿到第一控制权,对某些事件优先进行处理,比如全局的快捷键操作。
注意点:
事件过滤器可以安装在任何继承QObject的对象上,也可以安装在QApplication对象上(全局事件过滤器);
事件过滤器(eventFilter方法)返回值为true,表示将当前事件进行过滤,不会发送到对象本身;如果返回false,表示对当前事件不做任何处理,会通过event()方法将事件分发给原来的对象。如果不知道怎么处理或者返回什么,那就返回父类的eventFilter方法(类似 return QObject::eventFilter(watched, event));
一个对象可以安装多个事件过滤器(也就是一个对象的事件可以被多个对象进行监控/处理/过滤), 并且最先安装的事件过滤器是最后被调用的,类似于栈的操作,先进后出;
一个事件过滤器可以被多个对象安装,但是如果在事件过滤器(eventFilter方法)中把该对象删除了, 一定要将返回值设为true。否则 Qt会将事件继续分发给这个对象,从而导致程序崩溃。
16. 为什么 new QWidget 不需要 delete
Qt提供了一种机制,能够自动、有效的组织和管理继承自QObject的Qt对象,这种机制就是对象树。
Qt库中的很多类都以QObject作为它们的基类。QObject的对象总是以树状结构组织自己。
当我们创建一个QObject对象时,可以指定其父对象(也被称为父控件),新创建的对象将被加入到父对象的子对象(也被称为子控件)列表中。
当父对象被析构时,这个列表中的所有子对象会被析构。
不但如此,当某个QObject对象被析构时,它会将自己从父对象的列表中删除,以避免父对象被析构时,再次析构自己。
17.信号与槽的多种用法
一个信号可以和多个槽相连 这时槽的执行顺序和在不在同一个线程上有关,
同一线程,槽的执行顺序和声明顺序有关,
跨线程时,执行顺序是不确定的。
多个信号可以连接到一个槽 只要任意一个信号发出,这个槽就会被调用。
一个信号可以连接到另外的一个信号 当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
槽可以被取消链接 这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
想主动取消连接就用disconnect()函数中添加任何实现。
使用Lambda 表达式 在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
18.Qt connect 函数的连接方式
- 这里就是对connect第5个参数的使用。
Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。
如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。
如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数和信号发送者在同一线程。
效果看上去就像是直接在信号发送位置调用了槽函数,效果上看起来像函数调用,同步执行。
emit语句后面的代码将在与信号关联的所有槽函数执行完毕后才被执行。
Qt::QueuedConnection:信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,然后执行和信号关联的槽函数,这种方式既可以在同一线程内传递消息也可以跨线程操作。
emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。而且接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接。
19.事件与信号的区别
使用场合和时机不同 一般情况下,在“使用”窗口部件时,我们经常需要使用信号,并且会遵循信号与槽的机制;而在“实现”窗口部件时,我们就不得不考虑如何处理事件了。举个例子,当使用 QPushButton 时,我们对于它的 clicked()信号往往更为关注,而很少关心促成发射该信号的底层的鼠标或者键盘事件。但是,如果要实现一个类似于 QPushButton 的类,我们就需要编写一定的处理鼠标和键盘事件的代码,而且在必要的时候,仍然需要发射和接收 clicked()信号。
使用的机制和原理不同 事件类似于 Windows 里的消息,它的发出者一般是窗口系统。相对信号和槽机制,它 比较“底层”,它同时支持异步和同步的通信机制,一个事件产生时将被放到事件队列 里,然后我们就可以继续执行该事件 “后面”的代码。事件的机制是非阻塞的。 信号和槽机制相对而言比较“高层”,它的发出者一般是对象。从本质上看,它类似 于传统的回调机制,是不支持异步调用的。
在 Qt 中,事件(Event)和信号(Signal)是两个重要的概念,它们在事件驱动编程中扮演着重要角色,但二者在使用和工作原理上有所不同。
- 事件 (Event)
-
定义: 事件是 Qt 中的一个重要概念,它是系统或用户发生的某种行动的表示,比如鼠标点击、键盘输入、窗口改变等。
-
处理: 事件通过事件循环(Event Loop)进行处理。当事件发生时,Qt 会将事件包装成一个 QEvent 对象并将其放入事件队列。 QApplication 或 QWidget 的 event 方法会处理这些事件。
-
性质: 事件通常是系统层面的,涉及到应用程序与操作系统之间的交互。事件处理是由 Qt 的事件系统负责的,用户不需要手动发送事件。
-
使用: 你可以重载 QWidget 子类的 event 方法或者针对特定类型的事件重载相应的事件处理函数,比如 mousePressEvent, keyPressEvent 等。
- 信号 (Signal)
-
定义: 信号是对象之间通信的一种机制,用于表示某个事件发生了。这是一种用户定义的事件,通常与对象的状态变化相关联。
-
发射: 信号是用 emit 关键字发出的,当对象的状态改变时,可以通过发射信号来通知其他对象。例如,当按钮被点击时,可以发射一个 clicked() 信号。
-
连接: 信号与槽(Slot)之间的连接是 Qt 的核心功能之一。任何对象都可以连接到信号,以便在信号发射时执行某些操作。
-
使用: 信号的定义很简单,可以在类中使用 signals: 关键字定义,使用 QObject::connect() 函数将信号连接到相应的槽。
- 主要区别
- 事件是系统层面的,而信号是应用层面的:事件关联于系统和用户的操作,信号则是对象之间的通信机制。
- 事件是由 Qt 事件循环处理的,信号是通过连接的槽进行响应的:事件需要 Qt 进行管理,信号通过 connect 来实现响应。
- 事件通常是自动生成的,而信号需要手动发射:事件在发生后会自动进入事件队列,而信号需要在相应的状态变化时使用 emit 手动发射。
理解这些区别有助于在 Qt 中有效地进行事件驱动编程。
20.信号与槽机制需要注意的问题
信号与槽机制是比较灵活的,但有些局限性我们必须了解,这样在实际的使用过程中才能够做到有的放矢,避免产生一些错误。下面就介绍一下这方面的情况。
-
信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活 性,因此在速度上还是有所损失,当然这种损失相对来说是比较小的,通过在一台 i586- 133 的机器上测试是 10 微秒(运行 Linux),可见这种机制所提供的简洁性、灵活性还是 值得的。但如果我们要追求高效率的话,比如在实时系统中就要尽可能的少用这种机制。
-
信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能 产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射 所接收到的同样信号。
-
如果一个信号与多个槽相关联的话,那么,当这个信号被发射时,与之相关的槽被 激活的顺序将是随机的,并且我们不能指定该顺序。
-
宏定义不能用在 signal 和 slot 的参数中。
-
构造函数不能用在 signals 或者 slots 声明区域内。
-
函数指针不能作为信号或槽的参数。
-
信号与槽不能有缺省参数。
-
信号与槽也不能携带模板类参数。
21.信号的注意点
- 所有的信号声明都是公有的,所以Qt规定不能在signals前面加public,private, protected。
- 所有的信号都没有返回值,所以返回值都用void。
- 所有的信号都不需要定义。
- 必须直接或间接继承自QOBject类,并且开头私有声明包含Q_OBJECT。
- 在同一个线程中,当一个信号被emit发出时,会立即执行其槽函数,等槽函数执行完毕后,才会执行emit后面的代码,如果一个信号链接了多个槽,那么会等所有的槽函数执行完毕后才执行后面的代码,槽函数的执行顺序是按照它们链接时的顺序执行的。不同线程中(即跨线程时),槽函数的执行顺序是随机的。
- 在链接信号和槽时,可以设置链接方式为:在发出信号后,不需要等待槽函数执行完,而是直接执行后面的代码,是通过connect的第5个参数。
- 信号与槽机制要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,信号的参数可以比槽函数的参数多,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
22.Qt 实现多线程
QtConcurrent运行一个线程池,它是一个更高级别的API,不适合运行大量的阻塞操作:如果你做了很多阻塞操作,你很快就会耗尽池并让其他请求排队.在那种情况下,QThread(较低级别的构造)可能更适合于操作(每个代表一个线程).
23.描述QT中的文件流(QTextStream)和数据流(QDataStream)的区别
文件流(QTextStream):操作轻量级数据(int,double,QString)数据写入文本件中以后以文本的方式呈现。
数据流(QDataStream):通过数据流可以操作各种数据类型,包括对象,存储到文件中数据为二进制。
文件流,数据流都可以操作磁盘文件,也可以操作内存数据。通过流对象可以将对象打包到内存,进行数据的传输。
24.QT 保证多线程安全
- 互斥量(QMutex) QMutex m_Mutex; m_Mutex.lock(); m_Mutex.unlock();
- 互斥锁(QMutexLocker) QMutexLocker mutexLocker(&m_Mutex); 从声明处开始(在构造函数中加锁),出了作用域自动解锁(在析构函数中解锁)。
- 等待条件(QWaitCondition) QWaitCondtion m_WaitCondition; m_WaitConditon.wait(&m_muxtex, time);
- m_WaitCondition.wakeAll();
- QReadWriteLock类 ,
一个线程试图对一个加了读锁的互斥量进行上读锁,允许;
一个线程试图对一个加了读锁的互斥量进行上写锁,阻塞;
一个线程试图对一个加了写锁的互斥量进行上读锁,阻塞;、
一个线程试图对一个加了写锁的互斥量进行上写锁,阻塞。
读写锁比较适用的情况是:需要多次对共享的数据进行读操作的阅读线程。
QReadWriterLock 与QMutex相似,除了它对 “read”,"write"访问进行区别对待。它使得多个读者可以共时访问数据。
使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
- 信号量QSemaphore 但是还有些互斥量(资源)的数量并不止一个,比如一个电脑安装了2个打印机,我已经申请了一个,但是我不能霸占这两个,你来访问的时候如果发现还有空闲的仍然可以申请到的。于是这个互斥量可以分为两部分,已使用和未使用。
- QReadLocker便利类和QWriteLocker便利类对QReadWriteLock进行加解锁
25.详解Qt中的内存管理机制
- 这其实就是Qt对象树的机制
所有继承自QOBJECT类的类,如果在new的时候指定了父亲,那么它的清理时在父亲被delete的时候delete的,所以如果一个程序中,所有的QOBJECT类都指定了父亲,那么他们是会一级级的在最上面的父亲清理时被清理,而不用自己清理;
程序通常最上层会有一个根的QOBJECT,就是放在setCentralWidget()中的那个QOBJECT,这个QOBJECT在 new的时候不必指定它的父亲,因为这个语句将设定它的父亲为总的QAPPLICATION,当整个QAPPLICATION没有时它就自动清理,所以也无需清理。9这里QT4和QT3有不同,QT3中用的是setmainwidget函数,但是这个函数不作为里面QOBJECT的父亲,所以QT3中这个顶层的QOBJECT要自行销毁)。
这是有人可能会问那如果我自行delete掉这些QT接管负责销毁的指针了会出现什么情况呢,如果时这样的话,正常情况下QT的拥有这个对象的那个父亲会知道这件事情,它会直到它的儿子被你直接DELETE了,这样它会将这个儿子移出它的列表,并且重新构建显示内容,但是直接这样做时有风险的!也就是要说的下一条
当一个QOBJECT正在接受事件队列时如果中途被你DELETE掉了,就是出现问题了,所以QT中建议大家不要直接DELETE掉一个 QOBJECT,如果一定要这样做,要使用QOBJECT的deleteLater()函数,它会让所有事件都发送完一切处理好后马上清除这片内存,而且就算调用多次的deletelater也不会有问题。
QT不建议在一个QOBJECT 的父亲的范围之外持有对这个QOBJECT的指针,因为如果这样外面的指针很可能不会察觉这个QOBJECT被释放,会出现错误,如果一定要这样,就要记住你在哪这样做了,然后抓住那个被你违规使用的QOBJECT的destroyed()信号,当它没有时赶快置零你的外部指针。当然我认为这样做是及其麻烦也不符合高效率编程规范的,所以如果要这样在外部持有QOBJECT的指针,建议使用引用或者用智能指针,如QT就提供了智能指针针对这些情况
QT中的智能指针封装为QPointer类,所有QOBJECT的子类都可以用这个智能指针来包装,很多用法与普通指针一样,可以详见QT assistant 通过调查这个QT的内存管理功能,发现了很多东西,现在觉得虽然这个QT弄的有点小复杂,但是使用起来还是很方便的,***要说的是某些内存泄露的检测工具会认为QT的程序因为这种方式存在内存泄露,发现时大可不必理会。
26.Qt的内存管理
QT中使用对象父子关系进行内存管理
使用对象父子关系进行内存管理的原理,简述为:
在创建类的对象时,为对象指定父对象指针。当父对象在某一时刻被销毁释放时,父对象会先遍历其所有的子对象,并逐个将子对象销毁释放。(对象树)
使用引用计数对内存进行管理
引用计数
引用计数可以说是软件开发人员必知必会的知识点,它在内存管理领域的地位是数一数二的。
引用计数需要从三个方面来全面理解:
使用场景:一个资源,多处使用(使用即引用)。
问题:到底谁来释放资源。
原理:使用一个整形变量来统计,此资源在多少个地方被使用,此变量称为引用计数。当某处使用完资源以后,将引用计数减1。当引用计数为0时,即没有任何地方再使用此资源时,真正释放此资源。这里的资源,在动态内存管理中就是指堆内存。
用一句话描述就是:谁最后使用资源,谁负责释放资源。
显式共享
显式共享,是仅仅使用引用计数控制资源的生命周期的一种共享管理机制。这种机制下,无论资源在何处被引用,自始至终所有引用指向资源都是同一个。
之所以叫显式共享,是因为这种共享方式很直接,没有隐含的操作,如:Copy on Write写时拷贝(见隐式共享的相关说明)。如果想要拷贝并建立新的引用计数,必须手动调用detach()函数。
隐式共享
隐式共享,也是一种基于引用计数的控制资源的生命周期的共享管理机制。
隐式共享,对不同的操作有不同的处理:
读取时,在所有引用的地方使用同一个资源;
在写入、修改时自动复制一份资源出来做修改,自动脱离原始的引用计数,因为是新的资源,所以要建立新的引用计数。这种操作叫Copy on Write写时复制技术,是自动隐含进行的。
从使用者的角度看,每个使用者都像是拥有独立的一份资源。在一个地方修改,修改的只是原始资源的拷贝,不会影响原始资源的内容,自然就不会影响到其他使用者。所以这种共享方式称为隐式共享。
相关Qt类有QString、QByteArray、QImage、QList、QMap、QHash等。
智能指针
智能指针是对C/C++指针的扩展,同样基于引用计数。
智能指针和显示共享和隐式共享有何区别?它们区别是:智能指针是轻量级的引用计数,它将显式共享、隐式共享中的引用计数实现部分单独提取了出来,制作成模板类,形成了多种特性各异的指针。
例如,QString除了实现引用计数,还实现了字符串相关的丰富的操作接口。QList也实现了引用计数,还实现了列表这种数据结构的各种操作。可以说,显式共享和隐式共享一般是封装在功能类中的,不需要开发者来管理。
智能指针将引用计数功能剥离出来,为Qt开发者提供了便捷的引用计数基础设施。
强(智能)指针
Qt中的强指针实现类是:QSharedPointer,此类是模板类,可以指向多种类型的数据,主要用来管理堆内存。关于QSharedPointer在Qt Assistant中有详细描述。
它的原理和显式共享一样:最后使用的地方负责释放删除资源,如类对象、内存块。
强指针中的“强”,是指每多一个使用者,引用计数都会老老实实地**+1**。而弱指针就不同,下面就接着讲解弱指针。
弱(智能)指针
Qt中的弱指针实现类是QWeakPointer,此类亦为模板类,可以指向多种类型的数据,同样主要用来管理堆内存。关于QWeakPointer在Qt Assistant中有详细描述。
弱指针只能从强指针QSharedPointer转化而来,获取弱指针,不增加引用计数,它只是一个强指针的观察者,观察而不干预。只要强指针存在,弱指针也可以转换成强指针。可见弱指针和强指针是一对形影不离的组合,通常结合起来使用。
局部指针
局部指针,是一种超出作用域自动删除、释放堆内存、对象的工具。它结合了栈内存管理和堆内存管理的优点。
Qt中的实现类有:QScopedPointer,QScopedArrayPointer,具体可以参考Qt Assistant。
观察者指针
上面说弱指针的时候,讲到过观察者。观察者是指仅仅做查询作用的指针,不会影响到引用计数。
Qt中的观察者指针是QPointer,它必须指向QObject的子类对象,才能对对象生命周期进行观察。因为只有QObject子类才会在析构的时候通知QPointer已失效。
QPointer是防止悬挂指针(即野指针)的有效手段,因为所指对象一旦被删除,QPointer会自动置空,在使用时,判断指针是否为空即可,不为空说明对象可以使用,不会产生内存访问错误的问题。
27.描述QT的TCP通讯流程
服务端:(QTcpServer)
①创建QTcpServer对象
②监听list需要的参数是地址和端口号
③当有新的客户端连接成功回发送newConnect信号
④在newConnection信号槽函数中,调用nextPendingConnection函数获取新连接QTcpSocket对象
⑤连接QTcpSocket对象的readRead信号
⑥在readRead信号的槽函数使用read接收数据
⑦调用write成员函数发送数据
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
tcpServer = new QTcpServer;
tcpServer->listen(QHostAddress("192.168.0.111"),1234);
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(new_connect()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::new_connect()
{
qDebug("--new connect--");
QTcpSocket* tcpSocket = tcpServer->nextPendingConnection();
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(read_data()));
socketArr.push_back(tcpSocket);
}
void Widget::read_data()
{
for(int i=0; i<socketArr.size(); i++)
{
if(socketArr[i]->bytesAvailable())
{
char buf[256] = {};
socketArr[i]->read(buf,sizeof(buf));
qDebug("---read:%s---",buf);
}
}
}
客户端:(QTcpSocket)
①创建QTcpSocket对象
②当对象与Server连接成功时会发送connected 信号
③调用成员函数connectToHost连接服务器,需要的参数是地址和端口号
④connected信号的槽函数开启发送数据
⑤使用write发送数据,read接收数据
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket;
connect(tcpSocket,SIGNAL(connected()),this,SLOT(connect_success()));
tcpSocket->connectToHost("172.20.10.3",1234);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_send_clicked()
{
std::string msg = ui->msg->text().toStdString();
int ret = tcpSocket->write(msg.c_str(),msg.size()+1);
qDebug("--send:%d--",ret);
}
void Widget::connect_success()
{
ui->send->setEnabled(true);
}
28.描述UDP 之 UdpSocket通讯
UDP(User Datagram Protocol即用户数据报协议)是一个轻量级的,不可靠的,面向数据报的无连接协议。在网络质量令人十分不满意的环境下,UDP协议数据包丢失严重。由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。所以QQ这种对保密要求并不太高的聊天程序就是使用的UDP协议。
在Qt中提供了QUdpSocket 类来进行UDP数据报(datagrams)的发送和接收。Socket简单地说,就是一个IP地址加一个port端口 。
流程:
①创建QUdpSocket套接字对象
②如果需要接收数据,必须绑定端口
③发送数据用writeDatagram,接收数据用 readDatagram 。
29.多线程使用使用方法
方法一:
①创建一个类从QThread类派生
②在子线程类中重写 run 函数, 将处理操作写入该函数中
③在主线程中创建子线程对象, 启动子线程, 调用start()函数
方法二:
①将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数
②在主线程中创建一QThread类对象
③在主线程中创建一个业务类对象
④将业务类对象移动到子线程中
⑤在主线程中启动子线程
⑥通过信号槽的方式, 执行业务类中的业务处理函数
30.多线程使用注意事项
- 业务对象, 构造的时候不能指定父对象
- 子线程中不能处理ui窗口(ui相关的类)
- 子线程中只能处理一些数据相关的操作, 不能涉及窗口
- 核心点,不能指定父对象,子线程不能处理UI界面
31.多线程下,信号槽分别在什么线程中执行,如何控制
可以通过connect的第五个参数进行控制信号槽执行时所在的线程
connect有几种连接方式,直接连接和队列连接、自动连接
直接连接(Qt::DirectConnection):信号槽在信号发出者所在的线程中执行
队列连接 (Qt::QueuedConnection):信号在信号发出者所在的线程中执行,槽函数在信号接收者所在的线程中执行
自动连接 (Qt::AutoConnection):多线程时为队列连接函数,单线程时为直接连接函数。
32.Qt信号槽的调用流程
- MOC查找头文件中的signal与slots,标记出信号槽。将信号槽信息储存到类静态变量staticMetaObject中,并按照声明的顺序进行存放,建立索引。
- connect链接,将信号槽的索引信息放到一个双向链表中,彼此配对。
- emit被调用,调用信号函数,且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数。
- active函数在双向链表中找到所有与信号对应的槽索引,根据槽索引找到槽函数,执行槽函数。