C++面试题记录(Qt上位机方向)

简述Visual C++ 、Win32 API和MFC之间的关系?

(1) Visual C++是一个以C++程序设计语言为基础的、集成的、可视化的编程环境;
(2) Win32 API是32位Windows操作系以C/C++形式提供的一组应用程序接口;
(3) MFC是对Win32 API的封装,简化了开发过程。

Qt 的核心机制(元对象系统)

Qt的元对象系统(meta-object)提供了用于内部对象通讯的信号与槽(signals & slots)机制,运行时类型信息,以及动态属性系统(dynamic property system)。 整个元对象系统基于三个东西建立:

  1. QObject类为所有对象提供了一个基类,只要继承此类,那创建出的对象便可以使用元对象系统。
  2. 在声明类时,将Q_OBJECT宏放置于类的私有区域就可以在类中使能元对象特性,诸如动态属性,信号,以及槽。一般实际使用中,我们总是把Q_OBJECT宏放置在类声明时的开头位置,除此之外我们的类还需要继承QObject类。
  3. 元对象编译器(Meta-Object Compiler,缩写moc),为每个QObject的子类提供必要的代码去实现元对象特性。我们可以认为Qt对C++进行了一些拓展,moc则是负责将这些拓展语法翻译成原生的C++语法,之后交给C++编译器去编译。

Qt中内存管理机制

主要是通过对象树和智能指针。

对象树

Qt中的对象可以形成一个层次结构,一个对象可以拥有多个子对象。当父对象被销毁时,它会自动销毁其所有子对象。一个对象只能有一个父对象,不然释放就乱套了。

Qt里所有类的最终父类是QObject。ui设计里,QWidget是所有ui组件最大的父对象。QWidget继承于QObject。

在设置父对象时,父对象保存子对象地址,使用容器存储,因为可能有多个,方便管理。父对象析构时会自动析构子对象。

智能指针

Qt智能指针主要有  Qpointer(和普通指针类似,但是所指内容被释放后,自动指null)

QSharedPointer,QWeakPointer和C++的类似,QScopedPointer类似unique_ptr.

Qt窗口对象的父子关系如何指定?有什么作用与好处?

可以初始化时指定父对象,也可以用setParent函数。

子窗口随父窗口显示和隐藏,父窗口销毁也会一起销毁子窗口。

Qt中的事件机制

qt的事件机制基于事件循环,开启事件循环后,监听事件队列。当有事件发生时,Qt的事件系统会捕获并分发这些事件,然后通过事件过滤器、事件处理器或重写虚函数的方式进行处理。事件处理函数会在事件循环中被调用,以响应事件的发生。事件处理器可以处理多种类型的事件,具体的事件类型在函数的参数或者事件对象中提供。事件用于响应外部的、直接触发的操作,例如处理鼠标、键盘、窗口等外部事件。

Windows系统的事件循环由Windows API提供的消息循环机制来实现事件处理,将Windows的消息转换为Qt事件,而Linux系统由epoll多路复用机制完成.

Qt信号与槽机制

qt的信号与槽机制实现对象间的通信,和回调函数很像。信号是由Qt对象自身主动发出的,它表示一种对象状态的变化或特定的动作发生。connect创建一个信号与槽函数的连接,对象发出信号后,通过槽函数来处理信号,并执行相应的操作。不同的对象可以实现松耦合的通信方式。

单线程的信号与槽实现原理主要由观察者模式(一种设计模式)与函数指针的联合使用,槽函数会订阅自己感兴趣的信号,当信号触发时,会通过一个队列获取到函数参数,完成功能的实现。这样实现便可以做到一个信号可以连接多个槽,多个信号也可以连接一个槽。

多线程的信号与槽实现原理本质上是事件机制,信号发送时会触发一个事件,发送到槽函数所在线程的事件队列中,槽函数会排队执行对应的事件函数,完成信号与槽的调用。

Qt信号与槽机制的优缺点

优点

 1.类型安全:需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,则编译器会报错。
 2.松散耦合:信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是哪个对象的那个槽接收它发出的信号,它只需要在适当的时间发送适当的信号即可,而不需要关心是否被接收和哪个对象接收了。Qt保证适当的槽得到了调用,即使关联的对象在运行时删除,程序也不会崩溃。
 3.灵活性:一个信号可以关联多个槽,多个信号也可以关联同一个槽。

缺点

与直接调用回调函数相比,速度慢很多。原因是:

  • 需要定位接收信号的对象。
  • 安全地遍历所有槽。
  • 编组,解组传递参数。
  • 多线程的时候,信号需要排队等候。

观察者模式

观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己的状态。

信号与槽的连接方式(connect第五参数)

Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。

Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

Qt如果一个信号的处理方法一直未被执行,有哪些可能?

1.槽函数所在对象已经被销毁

2.连接断开了(可能是别的地方使用disconnect)

3.槽函数的线程没有开事件循环

4.事件循环阻塞导致消息未发出

5.在连接建立之前就发出信号,等连接建立后已经无法再收到了

Qt中多线程如何在创建子线程时传递参数,子线程在执行过程中如何向其它线程传递执行结果?

如何传递参数:

        在继承QThread或QRunnable的类中重写run()函数实现线程时,可以在此类添加成员变量,在开启线程之前给成员变量赋值,run()函数便可使用该成员变量。

        在使用moveToThread()方法使用线程时,线程的启动是通过信号调用槽开启的,直接通过信号与槽传参。

如何传递执行结果:

        直接通过信号与槽机制传递,不过要求槽函数所在的线程必须开启事件循环。

QPainter 是什么?它用于哪些场景?

QPainter 是 Qt 的绘图引擎,用于绘制各种图形元素和渲染文本。 它提供了很多常用的函数,例如画矩形、画圆等可以绘制基本图形。它还包含设置画笔宽度、颜色和样式、字体,渐变、图案、透明度的功能等,使用户可以使用相对简单的步骤呈现出复杂的效果。

使用 QPainter可以在窗口或其他部件上实现自定义的渲染,并创建自己的图标、图表和数据可视化工具,增强应用程序的用户体验。

Qt 中有哪些类型的定时器?它们之间有何区别

    QTimer:是一个通用的、基于事件的定时器,用于在指定时间间隔后触发 timeOut() 信号。

    QBasicTimer:是用于更高级别的定时操作的辅助类。它允许对象创建一个内部计时器,并使用 timerEvent() 函数处理超时事件。

两者均支持逐个次数触发或重复触发方法,但 QBasicTimer 可自行管理其内部定时器,这使得它更适合于需要细粒度的定时器操作。

QTimer需要自己编写方法,通过timeout信号连接槽(自己编写的方法),达到定时作用
QBasicTimer需要我们重写timerEvent()函数

Qt多线程四种方法

1.继承Qthread,重写run函数

2.继承QObject,调用moveToThread将创建的工作类对象移动到一个线程执行。线程启动和结束有信号可以连接到槽

3.QthreadPool线程池,需要创建工作类继承QRunnable,然后重写run函数,线程池调用start插入任务到线程池,start函数要传入工作类对象的指针

4.QtConcurrent是一个命名空间,提供一些并行计算的API,不用考虑创建线程的问题,调用QtConcurrent::run( func );  使用QFuture接收结果,和c++11的async很像

Qthread介绍

QThread:Qt提供的最基础的线程类,一个对象管理一个线程,自己维护线程启动停止,创建销毁,当然也能基于此类自己建立一个线程池。

一个类通过继承Qthread,重写run函数可以实现多线程,run函数的内容就是工作内容。创建类对象后,调用run函数启动子线程开始工作。

这个继承了Qthread的类一般是在主线程实例化,其成员函数也都是在主线程,只有其成员run函数运行在子线程中,run创建的变量也是属于子线程。

    当需要创建一个独立的线程来执行某个任务,且需要对线程的整个生命周期进行管理时,适合使用 QThread 方式。

    当任务逻辑相对简单或独立,不需要频繁地进行线程间通信时,可以选择使用 QThread 方式。

MoveToThread方法的多线程

一个工作类继承QObject,创建成员函数,可以有多个,执行不同任务。使用指针new一个Qthread, 然后实例化这个工作类的对象,调用moveToThread将对象移动到线程。连接线程的start函数和工作类的几个工作函数,调用start实现多线程工作。

    当需要将一个 QObject 对象移动到指定的线程中执行任务,或者需要多个对象在同一线程中协同工作时,适合使用 moveToThread() 方式。

     当需要灵活地控制对象和线程之间的关系,进行复杂的线程间通信时,可以选择使用 moveToThread() 方式。

QThreadPool线程池

Qt 中使用线程池需要先创建任务,添加到线程池中的每一个任务都需要是一个QRunnable类型,因此在程序中需要创建子类继承QRunnable这个类,然后重写run方法,在这个函数中编写要在线程池中执行的任务,并将这个子类对象传递给线程池,这样任务就可以被线程池中的某个工作的线程处理掉了。

任务类如果使用 Qt 的信号槽机制进行数据的传递就必须同时继承QObject这个类,如果不使用信号槽传递数据就可以只继承QRunnable即可。

QT为应用程序默认提供了一个全局的线程池,通过globalInstance获得其指针。

static QThreadPool * QThreadPool::globalInstance();

QtConcurrent

如果只是普通的任务,没有对象和线程间通信什么乱七八糟的,那QtConcurrent是首选。直接调用

QtConcurrent::run,第一个参数是线程池指针,没有传则默认用全局线程池,之后的可执行对象与参数,可以参考c++11的async,结果使用QFuture接收。

QtConcurrent 提供了一个将任务分发到处理器所有的核的易用接口。线程代码完全被隐藏在 QtConcurrent 框架下,所以你不必考虑细节。不能用于线程运行时需要通信或阻塞的情况。

信号与槽机制原理

  • moc查找头文件中的signals,slots,标记出信号和槽。
  • 将信号、槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引。
  • 当发现有connect连接时,将信号、槽的索引信息放到一个map中,彼此配对。
  • 当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到activate函数
  • 通过activate函数找到在map中找到所有与信号对应的槽索引
  • 根据槽索引找到槽函数,执行槽函数。

信号与槽机制需要注意的问题

信号与槽机制是比较灵活的,但有些局限性我们必须了解,这样在实际的使用过程中才能够做到有的放矢,避免产生一些错误。下面就介绍一下这方面的情况。

  1. 信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活 性,因此在速度上还是有所损失,当然这种损失相对来说是比较小的,通过在一台 i586- 133 的机器上测试是 10 微秒(运行 Linux),可见这种机制所提供的简洁性、灵活性还是 值得的。但如果我们要追求高效率的话,比如在实时系统中就要尽可能的少用这种机制。
  2. 信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能 产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射 所接收到的同样信号。
  3. 如果一个信号与多个槽相关联的话,那么,当这个信号被发射时,与之相关的槽被 激活的顺序将是随机的,并且我们不能指定该顺序。
  4. 宏定义不能用在 signal 和 slot 的参数中。
  5. 构造函数不能用在 signals 或者 slots 声明区域内。
  6. 函数指针不能作为信号或槽的参数。
  7. 信号与槽不能有缺省参数。
  8. 信号与槽也不能携带模板类参数。

事件循环

一般main函数最后return  a.exec();  这就是进入开启的事件循环。如果不进入事件循环,那函数很快就直接结束了,不会继续等待事件发生并进行分发。(主事件循环)

子线程在run函数中写exec,它会阻止run函数结束,让子线程始终等待事件队列的任务,从而实现利用信号槽进行线程通信。 (子线程的循环)

在一个线程中时间循环只能启动一个

事件循环如何启动

  • 返回值exec() 方法会返回一个整数值,表示对话框的退出状态。通常,返回 QDialog::Accepted 表示用户点击了对话框的“确定”按钮,返回 QDialog::Rejected 表示用户点击了“取消”按钮。

  • 适用场景:适用于需要等待用户对对话框进行操作的情况,通常用于获取用户的选择结果。

  • 非阻塞调用:使用 show() 方法显示对话框时,程序不会阻塞,可以继续执行后续代码。

  • 非模态对话框:通过 show() 方法显示的对话框是非模态的,用户可以在对话框打开的同时继续与其他窗口进行交互。

  • 不返回状态show() 方法不会返回对话框的退出状态,因为它不会阻塞程序的执行。

  • 适用场景:适用于需要在对话框显示的同时允许用户与其他窗口交互的情况,例如工具提示、信息提示等。

通过创建QEventLoop,调用QEventLoop的exec执行事件循环,其通过循环不断地调用QEventLoop::processEvents()来分发事件队列中的事件。

  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凛_Lin~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值