Qt面试题
1.QT信号槽机制的优缺点
优点:
1.类型安全:需要关联的信号槽的签名必须是等同的,即信号的参数类型和参数个数和接受该信号的槽的参数类型和参数个数相同。(PS:信号函数的参数个数必须大于等于槽函数的参数个数)
2.松散耦合:发射信号的对象不需要知道哪个槽接收,也不需要知道是否被接受,只需要适当的时候发送就行了。QT保证适当的槽得到调用,即使关联的对象在运行时被删除,程序也不会崩溃
3.灵活性:一个信号可以关联多个槽,多个信号也可以关联同一个槽
缺点:
1.速度较慢:与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍左右,原因:
- 需要定位接收信号的对象
- 安全的遍历所有槽
- 多线程的时候,信号需要排队等候
2.信号槽的参数限定很多,例如不能携带模板类参数,不能出现宏定义等。
2.多线程情况下, Qt中的信号槽分别在什么线程中执行, 如何控制?
可以通过connect函数的第五个参数来控制,信号槽执行时所在的线程
1)自动连接(AutoConnection),默认的连接方式,如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接;如果发送者与接收者处在不同线程,等同于队列连接。
2)直接连接(DirectConnection),当信号发射时,槽函数立即直接调用。无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行,即槽函数和信号发送者在同一线程
3)队列连接(QueuedConnection),信号发出后,信号会暂时放在一个消息队列里,需等到接收对象所属线程的事件循环取得控制权才能取得信号,然后执行相关联的槽函数。
4)Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。而且接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
5)Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接。
3.描述QT中的文件流(QTextStream)和数据流(QDataStream)的区别
文件流:操作轻量级数据(int,double,QString)写入文本文件中以后以文本的方式呈现。
数据流:通过数据流可以操作各种数据类型,包括对象,存储到文件中数据为二进制。
文件流和数据流都可以操作磁盘文件和内存数据。
4.描述下QT下TCP通信的整个流程
服务器端:
1.创建用于监听的套接字
2.给套接字设置监听
3.如果有连接到来,服务器端发出newconnected()信号
4.接收连接,通过nextPendingConnection()函数,返回一个QTcpSocket类型的套接字对象(用于通信)
5.使用用于通信的套接字对象通信
发送数据:write
接收数据:readAll/read
客户端:
1.创建用于通信的套接字
2.连接服务器connectToHost
3.连接成功与服务器通信
发送数据:write
接收数据:readAll/read
5.描述QT下UDP通信的整个流程
1.创建套接字对象
2.如果需要接收数据,必须绑定端口
3.发送数据:writeDatagram
4.接收数据:readDatagram
6.描述QT下多线程的两种使用方法,以及注意事项
第一种方法:
1.创建一个类从QThread类派生
2.在子类线程类中重写run函数,将处理操作写入该函数中
3.在主线程中创建子线程对象,启动子线程,调用start函数
第二种方法:
1.将业务处理抽象成一个业务类,在该类中创建一个业务处理函数
2.在主线程中创建一个QThread类对象
3.在主线程中创建一个业务类对象
4.将业务类对象移动到子线程中
5.在主线程中启动子线程
6.通过信号槽的方式,执行业务类中的业务处理函数
7.QT的优点、缺点
优点:
1.跨平台,几乎支持所有平台
2.接口简单,文档详细
3.开发效率高
缺点:
QT作为一个软件平台,比较庞大,臃肿
8.Qt的核心机制
信号和槽机制,在QT中使用信号和槽机制代替了回调机制。信号会在特定的事件出现时被发出,槽是在响应特定信号时会被调用的方法。
9.信号和槽机制原理
1.QT中的元对象编译器(moc)查找头文件中的信号和槽,标记出信号和槽
2.将信号和槽信息存储到类静态变量staticMetaObject中,并且按声明顺序存放,建立索引
3.当发现有connect连接时,将信号和槽的索引信息放到一个map中,彼此配对
4.当调用emit时,调用信号函数,并且传达发送信号的对象指针,信号索引,元对象指针,参数列表到activate函数
5.通过activate函数找到map中找到所有与信号对应的槽索引
6.根据槽索引找到槽函数,执行槽函数
10.信号和槽的本质是什么
回调函数。
信号:传递值或传递动作变化;槽函数响应信号或者接收值,或根据动作变化来做出相应操作
11.信号和槽与函数指针的比较
1.回调函数使用函数指针来实现,如果多个类都关注一个类的动态变化,这样就会需要写出一个比较长的列表来管理这些类之间的关系,在编码方面不灵活
2.QT使用信号与槽来解决这个连接问题,这种方式比较清晰简单一些,一个类只需要清楚自己有几个槽函数有几个信号,然后将信号与槽进行连接,QT会自己处理函数的调用关系。这样在软件设计角度更加的清晰,灵活,不容易出错。
3.Qt信号与槽机制降低了Qt对象的耦合度。发信号的对象不需要知道有几个槽函数,也不需要关系是否收到信号,或者谁收到了,谁没收到。同样的槽函数也不需要知道谁是信号的发出者。信号只需要在合适的时机发出即可,降低了对象之间的耦合度。
12.QT事件过滤器
1.父窗口类通过重写eventFilter方法来监听子控件的相关事件进行处理。 使用这种方式的好处是不需要通过重写控件的方式获取某些事件,对于安装了事件过滤器的对象,他们所有的事件都会经过这个事件过滤器,所以就可以直接在父窗口中进行监测。比如某个窗口中有个QLabel对象,我们想要监听他的鼠标点击事件,那我们就需要继承QLabel类,然后重写mousePressEvent事件,如果有其他类型的控件也需要获取某个事件,那是不是都需要继续控件并重写某个事件了,所以我们通过事件过滤器就可以很方便获取某个对象的某个事件。
2.专门的事件过滤器类,对特定的对象/特定的事件进行处理。 事件过滤器类只需对当前安装的对象进行处理,无需关心其他操作,且一个事件过滤器类可以被多个对象使用,例如Qt文档中的按键过滤示例,KeyPressEater类中的eventFilter过滤了所有的键盘按下事件,只要安装此事件过滤器的控件,都接收不到键盘按键按下的事件,这种就是对某个通用的事件进行过滤,可以进行多次复用。
3.给QApplication安装事件过滤器,达到全局事件监听的效果。 在notify方法下发事件的时候,QApplication对象可以拿到第一控制权,对某些事件优先进行处理,比如全局的快捷键操作。
(好处:不用针对每个控件都重写某个事件,只需要对需要使用该事件的控件安装事件过滤器就行,还可以一个事件过滤器类可以被多个对象使用)
注意点:
1.一个对象可以安装多个事件过滤器(也就是一个对象的事件可以被多个对象进行监控/处理/过滤), 并且最先安装的事件过滤器是最后被调用的,类似于栈的操作,先进后出;
2.一个事件过滤器可以被多个对象安装,但是如果在事件过滤器(eventFilter方法)中把该对象删除了, 一定要将返回值设为true。否则 Qt会将事件继续分发给这个对象,从而导致程序崩溃。
13.为什么new QWidget不需要delete
Qt提供了一种机制,能够自动、有效的组织和管理继承自QObject的Qt对象,这种机制就是对象树。 Qt库中的很多类都以QObject作为它们的基类。QObject的对象总是以树状结构组织自己。当我们创建一个QObject对象时,可以指定其父对象(也被称为父控件),新创建的对象将被加入到父对象的子对象(也被称为子控件)列表中。当父对象被析构时,这个列表中的所有子对象会被析构。不但如此,当某个QObject对象被析构时,它会将自己从父对象的列表中删除,以避免父对象被析构时,再次析构自己。
14.信号和槽的几种连接方式优缺点
1.使用SIGNAL和SLOT宏(宏函数将信号和槽函数转换为const char *类型的字符串)
connect(ui->pushbutton,SIGNAL(clicked),this,SLOT(onPushButtonClicked()))
2.使用&类名::函数名
connect(ui->pushbutton,&QPushButton::clicked,this,onSetBlockedSignalStatus)
优点:
1.不需要写参数更简便
2.不需要槽函数的参数类型与信号对应的参数类型完全一致,只需要进行隐藏式转换
3.可以在编译时进行检查,比如信号或者槽函数的拼写错误、槽函数参数数量多于信号的参数数量等都能在编译时期发现,而不是运行时。
3.Lambda表达式,关联后直接编写信号发射后要执行的代码,不需要定义槽函数
15.事件与信号的区别
1.使用场合和时机不同 一般情况下,在“使用”窗口部件时,我们经常需要使用信号,并且会遵循信号与槽的机制;而在“实现”窗口部件时,我们就不得不考虑如何处理事件了。举个例子,当使用 QPushButton 时,我们对于它的 clicked()信号往往更为关注,而很少关心促成发射该信 号的底层的鼠标或者键盘事件。但是,如果要实现一个类似于 QPushButton 的类,我们就需要编写一定的处理鼠标和键盘事件的代码,而且在必要的时候,仍然需要发射和接收 clicked()信号。
2.使用的机制和原理不同 事件类似于 Windows 里的消息,它的发出者一般是窗口系统。相对信号和槽机制,它 比较“底层”,它同时支持异步和同步的通信机制,一个事件产生时将被放到事件队列 里,然后我们就可以继续执行该事件 “后面”的代码。事件的机制是非阻塞的。 信号和槽机制相对而言比较“高层”,它的发出者一般是对象。从本质上看,它类似 于传统的回调机制,是不支持异步调用的。
16.信号和槽机制需要注意的问题
信号与槽机制是比较灵活的,但有些局限性我们必须了解,这样在实际的使用过程中才能够做到有的放矢,避免产生一些错误。下面就介绍一下这方面的情况。
1.信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活 性,因此在速度上还是有所损失,当然这种损失相对来说是比较小的,通过在一台 i586- 133 的机器上测试是 10 微秒(运行 Linux),可见这种机制所提供的简洁性、灵活性还是 值得的。但如果我们要追求高效率的话,比如在实时系统中就要尽可能的少用这种机制。
2.信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能 产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射 所接收到的同样信号。
3.如果一个信号与多个槽相关联的话,那么,当这个信号被发射时,与之相关的槽被 激活的顺序将是随机的,并且我们不能指定该顺序。
4.宏定义不能用在 signal 和 slot 的参数中。
5.构造函数不能用在 signals 或者 slots 声明区域内。
6.函数指针不能作为信号或槽的参数。
7.信号与槽不能有缺省参数。
8.信号与槽也不能携带模板类参数。
17.信号的注意点
1.所有的信号声明都是公有的,所以Qt规定不能在signals前面加public,private, protected。
2.所有的信号都没有返回值,所以返回值都用void。
3.所有的信号都不需要定义。
4.必须直接或间接继承自QOBject类,并且开头私有声明包含Q_OBJECT。
5.在同一个线程中,当一个信号被emit发出时,会立即执行其槽函数,等槽函数执行完毕后,才会执行emit后面的代码,如果一个信号链接了多个槽,那么会等所有的槽函数执行完毕后才执行后面的代码,槽函数的执行顺序是按照它们链接时的顺序执行的。不同线程中(即跨线程时),槽函数的执行顺序是随机的。
6.在链接信号和槽时,可以设置链接方式为:在发出信号后,不需要等待槽函数执行完,而是直接执行后面的代码,是通过connect的第5个参数。
7.信号与槽机制要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,信号的参数可以比槽函数的参数多,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
18.QT中的多线程
qt中的线程与线程同步-CSDN博客
多线程理解-CSDN博客
QMutex、QMutexLocker、QReadWriteLock、QWaitCondition
19.详解Qt中的内存管理机制
1.所有继承自QOBJECT类的类,如果在new的时候指定了父亲,那么它的清理时在父亲被delete的时候delete的,所以如果一个程序中,所有的QOBJECT类都指定了父亲,那么他们是会一级级的在最上面的父亲清理时被清理,而不用自己清理;
2.程序通常最上层会有一个根的QOBJECT,就是放在setCentralWidget()中的那个QOBJECT,这个QOBJECT在 new的时候不必指定它的父亲,因为这个语句将设定它的父亲为总的QAPPLICATION,当整个QAPPLICATION没有时它就自动清理,所以也无需清理。9这里QT4和QT3有不同,QT3中用的是setmainwidget函数,但是这个函数不作为里面QOBJECT的父亲,所以QT3中这个顶层的QOBJECT要自行销毁)。
3.这是有人可能会问那如果我自行delete掉这些QT接管负责销毁的指针了会出现什么情况呢,如果时这样的话,正常情况下QT的拥有这个对象的那个父亲会知道这件事情,它会直到它的儿子被你直接DELETE了,这样它会将这个儿子移出它的列表,并且重新构建显示内容,但是直接这样做时有风险的!也就是要说的下一条
4.当一个QOBJECT正在接受事件队列时如果中途被你DELETE掉了,就是出现问题了,所以QT中建议大家不要直接DELETE掉一个 QOBJECT,如果一定要这样做,要使用QOBJECT的deleteLater()函数,它会让所有事件都发送完一切处理好后马上清除这片内存,而且就算调用多次的deletelater也不会有问题。
5.QT不建议在一个QOBJECT 的父亲的范围之外持有对这个QOBJECT的指针,因为如果这样外面的指针很可能不会察觉这个QOBJECT被释放,会出现错误,如果一定要这样,就要记住你在哪这样做了,然后抓住那个被你违规使用的QOBJECT的destroyed()信号,当它没有时赶快置零你的外部指针。当然我认为这样做是及其麻烦也不符合高效率编程规范的,所以如果要这样在外部持有QOBJECT的指针,建议使用引用或者用智能指针,如QT就提供了智能指针针对这些情况,见***一条。
6.QT中的智能指针封装为QPointer类,所有QOBJECT的子类都可以用这个智能指针来包装,很多用法与普通指针一样,可以详见QT assistant 通过调查这个QT的内存管理功能,发现了很多东西,现在觉得虽然这个QT弄的有点小复杂,但是使用起来还是很方便的,***要说的是某些内存泄露的检测工具会认为QT的程序因为这种方式存在内存泄露,发现时大可不必理会。
20.QPixmap、QImage、QPicture、QBitmap的区别
1.QPixmap专门为图像在屏幕上的显示做了优化
2.QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用 QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。
3.QImage专门为图像的像素级访问做了优化。
4.QPicture则可以记录和重现QPainter的各条命令。
图片绘图类QPixmap、QImage、QPicture-CSDN博客
21..qrc,.ui文件格式和用途?QResource资源文件系统的优势
qrc 文件是基于 XML 格式的资源系统配置文件,该文件中指定了各种资源的信息。
ui文件是一个 XML 文件,定义了窗口上的所有组件的属性设置、布局,及其信号与槽函数的关联等
Qt资源系统是一种将图片、数据存储于二进制文件中的一套系统。这些图片、数据会被我们的程序使用,它们称为资源。Qt资源系统中存储了这些资源,使得程序可以很方便地找到并使用它们。
22.show()、exec()的区别
show():显示一个非模式对话框。控制权即刻返回给调用函数。
exec():显示一个模式对话框,并且锁住程序直到用户关闭该对话框为止。函数返回一个DialogCode结果。
23.dleteLater和delete的区别
1.delete:在QT中,delete可以用于释放动态分配的QObject对象。使用delete会立即删除对象,但是如果在对象的生命周期内,仍有其他对象与此对象有关联,那么这些对象可能会访问已经释放的内存吗,导致程序崩溃
2.deleteLater():deleteLater是QObject类中的一个成员函数,用于在一个事件循环中异步删除对象。使用deleteLater()会在对象所属的线程的事件循环中添加一个事件,当事件循环处理完当前事件后,才会执行对象的删除。使用deleteLater()可以避免在对象的生命周期内,其他对象访问已经释放的内存的问题
因此,使用deleteLater()可以更加安全的删除QObject对象,避免程序崩溃的风险。但是,某些情况下,如果需要立即删除对象,使用delete可能更好。
24.QT提供的进程通信方式有哪些
1.信号和槽(Signals and Slots):他通过连接一个对象的信号和另一个对象的槽函数来实现两个对象之间的通信。可以在同一个进程内或不同进程之间发送和接收信号
2.共享内存(Shared Memory):使用QSharedMemory类可以在进程之间共享内存,以便他们可以访问和修改相同的数据
3.TCP/IP套接字(TCP/IP Sockets):QT提供了QTcpSocket和QTcpServer类,可以用于在不同计算机或同一计算机上的不同进程之间建立TCP/IP连接。
25.QT中的智能指针
Qt 的智能指针包括:
- QSharedPointer
- QScopedPointer
- QScopedArrayPointer
- QWeakPointer
- QPointer
- QSharedDataPointer
QSharedPointer:
QSharedPointer内部维持着对拥有的内存资源的引用计数,当引用计数下降到0时,这个内存资源就被释放了。
QSharedPointer 的构造函数有如下这几种。
QSharedPointer();
QSharedPointer(X *ptr);
QSharedPointer(X *ptr, Deleter deleter);
QSharedPointer(std::nullptr_t);
QSharedPointer(std::nullptr_t, Deleter d);
QSharedPointer(const QSharedPointer<T> &other);
QSharedPointer(const QWeakPointer<T> &other);
QSharedPointer 是线程安全的,因此即使有多个线程同时修改 QSharedPointer 对象也不需要加锁。这里要特别说明一下,虽然 QSharedPointer 是线程安全的,但是 QSharedPointer 指向的内存区域可不一定是线程安全的。所以多个线程同时修改 QSharedPointer 指向的数据时还要应该考虑加锁的。
下面是个 QSharedPointer 的例子,演示了如何自定义 Deleter。
static void doDeleteLater(MyObject *obj)
{
obj->deleteLater();
}
void otherFunction()
{
QSharedPointer<MyObject> obj = QSharedPointer<MyObject>(new MyObject, doDeleteLater);
// continue using obj
obj.clear(); // calls obj->deleteLater();
}
QScopedPointer:
QScopedPointer 类似于 C++ 11 中的 unique_ptr。当我们的内存数据只在一处被使用,用完就可以安全的释放时就可以使用 QScopedPointer。只要出了作用域,指针就会被自动删除。
void myFunction(bool useSubClass)
{
MyClass *p = useSubClass ? new MyClass() : new MySubClass;
QIODevice *device = handsOverOwnership();
if (m_value > 3) {
delete p;
delete device;
return;
}
try {
process(device);
}
catch (...) {
delete p;
delete device;
throw;
}
delete p;
delete device;
}
如果用 QScopedPointer,就可以简化为:
void myFunction(bool useSubClass)
{
// assuming that MyClass has a virtual destructor
QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
QScopedPointer<QIODevice> device(handsOverOwnership());
if (m_value > 3)
return;
process(device);
}
QScopedArrayPointer:
如果我们指向的内存数据是一个数组,这时可以用 QScopedArrayPointer。QScopedArrayPointer 与 QScopedPointer 类似,用于简单的场景。只要出了作用域,指针就会被自动删除。
void foo()
{
QScopedArrayPointer<int> i(new int[10]);
i[2] = 42;
...
return; // our integer array is now deleted using delete[]
}
QPointer:
QPointer 与其他的智能指针有很大的不同。其他的智能指针都是为了自动释放内存资源而设计的。 QPointer 智能用于指向 QObject 及派生类的对象。当一个 QObject 或派生类对象被删除后,QPointer 能自动把其内部的指针设为 0。这样我们在使用这个 QPointer 之前就可以判断一下是否有效了。
为什么非要是 QObject 或派生类呢,因为 QObject 可以构成一个对象树,当这颗对象树的顶层对象被删除时,它的子对象自动的被删除。所以一个 QObject 对象是否还存在,有时并不是那么的明显。有了 QPointer 我们在使用一个对象之前,至少可以判断一下。
要特别注意的是,当一个 QPointer 对象超出作用域时,并不会删除它指向的内存对象。这和其他的智能指针是不同的。我们在手动delete一个指针的时候,需要再将其置空,要不然会变成一个悬挂的野指针,那么QPointer就是帮忙干这事的,会在对象被销毁时,自动设置为NULL。
下面给一个简单的例子:
QPointer<QLabel> label = new QLabel;
label->setText("&Status:");
...
if (label)
label->show();
QSharedDataPointer
QSharedDataPointer 这个类是帮我们实现数据的隐式共享的。我们知道 Qt 中大量的采用了隐式共享和写时拷贝技术。比如下面这个例子:
QString str1 = "abcdefg";
QString str2 = str1;
QString str2[2] = 'X';
第二行执行完后,str2 和 str1 指向的同一片内存数据。当第三句执行时,Qt 会为 str2 的内部数据重新分配内存。这样做的好处是可以有效的减少大片数据拷贝的次数,提高程序的运行效率。
Qt 中隐式共享和写时拷贝就是利用 QSharedDataPointer 和 QSharedData 这两个类来实现的。
比如我们有个类叫做 Employee,里面有些数据希望能够利用隐式共享和写时拷贝技术。那么我们可以把需要隐式共享的数据封装到另一个类中,比如叫做 EmployeeData,这个类要继承自 QSharedData。
class EmployeeData : public QSharedData
{
public:
EmployeeData() : id(-1) { }
EmployeeData(const EmployeeData &other)
: QSharedData(other), id(other.id), name(other.name) { }
~EmployeeData() { }
int id;
QString name;
};
QWeakPointer
QWeakPointer不能用于直接取消引用指针,但它可用于验证指针是否已在另一个上下文中被删除。并且QWeakPointer对象只能通过QSharedPointer的赋值来创建。
需要注意的是,QWeakPointer不提供自动转换操作符来防止错误发生。即使QWeakPointer跟踪指针,也不应将其视为指针本身,因为它不能保证指向的对象保持有效。