优点:①类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错。
②松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。
③灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽。
不足:速度较慢。与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍。
原因:①需要定位接收信号的对象。②安全地遍历所有关联槽。③编组、解组传递参数。④多线程的时候,信号需要排队等待。(然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。)
回调函数。信号或是传递值,或是传递动作变化;槽函数响应信号或是接收值,或者根据动作变化来做出对应操作。
文件流(QTextStream)。操作轻量级数据(int,double,QString)数据写入文本件中以后以文本的方式呈现。
数据流(QDataStream)。通过数据流可以操作各种数据类型,包括对象,存储到文件中数据为二进制。
文件流,数据流都可以操作磁盘文件,也可以操作内存数据。通过流对象可以将对象打包到内存,进行数据的传输。
方法一:
①创建一个类从QThread类派生
②在子线程类中重写 run 函数, 将处理操作写入该函数中
③在主线程中创建子线程对象, 启动子线程, 调用start()函数
方法二:
①将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数
②在主线程中创建一QThread类对象
③在主线程中创建一个业务类对象
④将业务类对象移动到子线程中
⑤在主线程中启动子线程
⑥通过信号槽的方式, 执行业务类中的业务处理函数
简述:两种基本方式,一种是QObject继承,将对象MoveToThread(&QThread),另一种是QThread继承,并重写run函数。
多线程使用注意事项:
* 1. 业务对象, 构造的时候不能指定父对象
* 2. 子线程中不能处理ui窗口(ui相关的类)
* 3. 子线程中只能处理一些数据相关的操作, 不能涉及窗口
可以通过connect的第五个参数进行控制信号槽执行时所在的线程
connect有几种连接方式,直接连接和队列连接、自动连接
直接连接(Qt::DirectConnection):信号槽在信号发出者所在的线程中执行
注:当信号发出后,相应的槽函数将立即被调用。emit语句后的代码将在所有槽函数执行完毕后被执行。
队列连接(Qt::QueuedConnection):信号在信号发出者所在的线程中执行,槽函数在信号接收者所在的线程中执行
注:当信号发出后,排队到信号队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,调用相应的槽函数。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕
自动连接 (Qt::AutoConnection):多线程时为队列连接函数,单线程时为直接连接函数。
注:Qt的默认连接方式,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
队列连接 (Qt: BlockingQueuedConnection):与Qt::QueuedConnection相同,除了信号线程阻塞直到槽返回。如果接收方处于发送信号的线程中,则不能使用此连接,否则应用程序将死锁。
注:这个是完全同步队列只有槽线程执行完成才会返回,否则发送线程也会一直等待,相当于是不同的线程可以同步起来执行。
另外一种解释:
Qt::AutoConnection:默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
常见的QT事件类型如下:
键盘事件: 按键按下和松开 鼠标事件: 鼠标移动,鼠标按键的按下和松开
拖放事件: 用鼠标进行拖放 滚轮事件: 鼠标滚轮滚动
绘屏事件: 重绘屏幕的某些部分 定时事件: 定时器到时
焦点事件: 键盘焦点移动 进入和离开事件: 鼠标移入widget之内,或是移出
移动事件: widget的位置改变 大小改变事件: widget的大小改变
显示和隐藏事件: widget显示和隐藏 窗口事件: 窗口是否为当前窗口
1)重载特定事件处理函数.
最常见的事件处理办法就是重载象mousePressEvent(), keyPressEvent(), paintEvent() 这样的特定事件处理函数.
2)重载event()函数.
通过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它. 比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数. 当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件.
3) 在Qt对象上安装事件过滤器.
安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)
首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理.
然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码.
4) 给QAppliction对象安装事件过滤器.
一旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个 eventFilter(). 在debug的时候,这个办法就非常有用, 也常常被用来处理失效了的widget的鼠标事件,通常这些事件会被QApplication::notify()丢掉. ( 在QApplication::notify() 中, 是先调用qApp的过滤器, 再对事件进行分析, 以决定是否合并或丢弃)
5) 继承QApplication类,并重载notify()函数.
Qt 是用QApplication::notify()函数来分发事件的.想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法. 通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类. 而且可以给QApplication对象安装任意个数的事件。
1)*互斥量(QMutex)
QMutex m_Mutex;
m_Mutex.lock();
m_Mutex.unlock();
2)*互斥锁(QMutexLocker)
QMutexLocker mutexLocker(&m_Mutex);
从声明处开始(在构造函数中加锁),出了作用域自动解锁(在析构函数中解锁)。
3)等待条件(QWaitCondition)
QWaitCondtion m_WaitCondition;
m_WaitConditon.wait(&m_muxtex, time);
m_WaitCondition.wakeAll();
4)*(QReadWriteLock)类
》一个线程试图对一个加了读锁的互斥量进行上读锁,允许;
》一个线程试图对一个加了读锁的互斥量进行上写锁,阻塞;
》一个线程试图对一个加了写锁的互斥量进行上读锁,阻塞;、
》一个线程试图对一个加了写锁的互斥量进行上写锁,阻塞。
读写锁比较适用的情况是:需要多次对共享的数据进行读操作的阅读线程。
QReadWriterLock 与QMutex相似,除了它对 “read”,”write”访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
5)*信号量QSemaphore
但是还有些互斥量(资源)的数量并不止一个,比如一个电脑安装了2个打印机,我已经申请了一个,但是我不能霸占这两个,你来访问的时候如果发现还有空闲的仍然可以申请到的。于是这个互斥量可以分为两部分,已使用和未使用。
6)*QReadLocker便利类和QWriteLocker便利类对QReadWriteLock进行加解锁
设计模式平时有使用到吗?能不能说下常见的设计模式有哪些?能不能说说大致的概念?能不能具体说下工作中如何使用的?
答:总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
(项目或Qt库中虚函数一般用于基类,有用,作用不大,可供了解)
什么情况下使用虚函数?和纯虚函数有什么区别?虚析构函数的作用是什么?(虚函数表)
答:虚函数的主要作用是“运行时多态”。虚析构函数的作用在于使用delete删除一个对象时,能确保析构函数被正确的执行。
区别:
1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。
2. 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
3. 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。
4. 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
5. 虚函数的定义形式:virtual {method body}
纯虚函数的定义形式:virtual { } = 0;
在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。
6. 虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol “public: virtual void __thiscall
ClassName::virtualFunctionName(void)”
7. 对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
8. 实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖
该虚函数,由多态方式调用的时候动态绑定。
9. 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
10. 多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现
b 运行时多态性:通过虚函数实现。
11. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。
参考链接:
QT常见面试题,基础知识偏多
QT 面试题汇总
深入:
Qt面试题
字节跳动C++/Qt PC客户端面试题精选