qt 常见知识点

对象模型(对象树)

1、QObject是以对象树的形式组织起来的。
2、当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
3、当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)

4.我们也可以自己删除子对象,它们会自动从其父对象列表中删除

5.当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
6.任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。

所以:在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在上创建。

connect几个类型的区别

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

  2、Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数和信号发送者在同一线程。效果看上去就像是直接在信号发送位置调用了槽函数,效果上看起来像函数调用,同步执行。emit语句后面的代码将在与信号关联的所有槽函数执行完毕后才被执行。

  3、Qt::QueuedConnection:信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,然后执行和信号关联的槽函数,这种方式既可以在同一线程内传递消息也可以跨线程操作。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕

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

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

自绘控件

Qt常用线程几种方式,数据竞争,加锁同步等等

继承QThread的多线程

线程创建:

 1.QThread只有run函数是在新线程里的,但一般调用start函数后,会自动调用run函数,从而使线程起来。run()为虚函数。

2.如果线程已经运行,你重复调用start其实是不会进行任何处理,所以建议在start之前进行判断:使用isRunning函数。

线程退出:

1.在run函数调用exit()或者quit()函数可以结束线程,或在主线程调用terminate强制结束线程。

2.在线程运行过程调用quit、exit函数有什么效果
答案是:不会发生任何效果,QThread不会因为你调用quit或exit函数而退出正在运行到一半的run。最简单的方法是添加一个bool变量,通过主线程修改这个bool变量来进行终止,但这样有可能引起访问冲突,需要加锁(QMutexLocker locker(&m_lock);

3.如果是线程对象在创建时无父对象,需要自己释放内存。可以使用信号finished,槽deleteLater,自动销毁分配的内存。

​​​​connect(m_pSaveThread,&SaveDataThread::finished,m_pSaveThread,&QObject::deleteLater);//自动销毁分配的内存

另外注意信号destroyed的使用

connect(m_pSaveThread,&SaveDataThread::destroyed,this,&MainWindow::SaveDataThreadDestroy);  //SaveDataThreadDestroy自定义的槽函数

用一个临时变量记录当前正在运行的局部线程,由于线程结束时会销毁自己,因此要通知主线程把这个保存线程指针的临时变量设置为NULL
因此用到了QObject::destroyed信号,在线程对象析构时通知UI把m_printThread 设置为nullptr;

void MainWindow::SaveDataThreadDestroy(QObject *obj)
{  
  if(qobject_cast(m_pSaveThread) == obj )   
 {      
  m_pSaveThread = NULL;   
 }
}

 4.如果线程对象时设置了父对象。退出时需要调用wait阻塞等待

m_thread = new ThreadFromQThread(this);

m_thread->start();

退出:

m_thread->stopImmediately();

m_thread->wait(); 

全局线程和局部线程的区别

1.全局线程需要调wait阻塞等待,防止主线程窗口销毁了,把线程对象也释放了,而线程还没出来,会引起崩溃。

2.局部线程退出时,需要自己释放内存,并且通知主线程把线程指针置NULL

继承QObject的多线程

1.用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer、QTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。

2.使用QObject来实现多线程比用继承QThread的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据

3.不能给它设置任何父对象.可以通过moveToThread,将它的线程相关性切换到指定线程。


多线程特点:

1.QMutex会带来一定的耗时,大概速度会降低1.5倍(Release模式)

2.子对象不能脱离父对象,单独切换到与父对象不同的线程中

3. QObject的子对象必须在创建其parent的线程中创建。即QObject的线程相关性默认会和它的parent保持一致

4.在Qt中,当一个对象被移到另一个线程时,他的所有子对象也会一并转移到另外那个线程。

5.在qt中 线程创建的对象必须在线程中释放。

同步

1.互斥量QMutex  

trylock、lock、unlock

2.QMutexLocker   

是对QMutex的简化,QMutexLocker   的构造函数接收一个互斥量作为参数并将其锁定。而不是lock和unlock的调用

QMutexLocker locker(&m_lock);

缺点:每次只有一个线程获取到互斥量的权限。

3.QReadWriteLock 读写锁

特点:可以多个线程以只读方式访问,但只有一个线程以写的方式访问。

LockForRead();   如果有其他线程以写入的方式锁定,才阻塞

LockForWrite();   阻塞

unlock();

另外,QReadlocker 和QWriteLocker是QReadWriteLock的简化,如同QMutexLocker  是QMutex   的简化一样。无须使用unlock().

4. QWaitCondition

是通过一个线程达到某种条件来唤起另一个线程来实现同步的。结合QMutex 一起使用。

bool wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX);  //解锁互斥量,并阻塞等待唤醒,唤醒后锁定互斥量。需要用到一个互斥量作为参数,而这个互斥量的状态必须是locked的。

即调用wait函数前,互斥量的状态必须是locked的。调用wait,解锁互斥量(其他线程就可以正常访问了),并阻塞等待唤醒。其他地方调用wakeOne或者wakeAll后,唤醒并锁定互斥量。需要调用unlock函数进行解锁。

       mutex.lock();
        /*QWaitCondition 用于多线程的同步,一个线程调用QWaitCondition::wait() 阻塞等待,
         * 直到另一个线程调用QWaitCondition::wake() 唤醒才继续往下执行。*/
        newdateAvaiable.wait(&mutex);//先解锁mutex,是其他线程能够使用mutex
        emit newValue(seq,diceValue);
        mutex.unlock();

bool wait(QReadWriteLock *lockedReadWriteLock, unsigned long time = ULONG_MAX);
void wakeOne(); //唤醒一个等待线程,具体哪一个线程,由操作系统决定
void wakeAll();//唤醒所有等待线程

用于生产者-消费者,比互斥量效率更高

5. QSemaphore 信号量   

比互斥量更高级,互斥量只能锁定一次,信号量可以多次使用,每次创建时可以设定初始值 void acquire(int n = 1);  //获取n个资源

bool tryAcquire(int n = 1); 

bool tryAcquire(int n, int timeout);

void release(int n = 1);  //释放资源

参考:qt 两种不同方式的多线程_小飞侠hello的博客-CSDN博客

信号槽机制

moc查找头文件中的signals,slots,标记出信号和槽。

将信号槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引。

当发现有connect连接时,将信号槽的索引信息放到一个map中,彼此配对。

当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数。即在信号函数中调用active函数。

通过active函数找到在map中找到所有与信号对应的槽索引

根据槽索引找到槽函数,执行槽函数。

参考:qt 的核心:元对象系统、属性系统、对象树、信号槽_小飞侠hello的博客-CSDN博客_qt实现rtdb

对Qt事件循环、事件分发的理解

1.QEventLoop (即Qt中的事件循环类)

消息循环在QEventLoop类中实现。通过QEventLoop::exec()可以进入一个消息循环的阻塞状态中,也就是不断地PeekMessage、TranslateMessage、DispatchMessage(和windows 消息机制差不多的)。

2.QEvent

QT将系统产生的消息转化为QT事件,QT事件被封装为对象,所有的QT事件均继承抽象类QEvent。比如键盘、鼠标产生的keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent)

3.事件过滤器

Qt还提供了事件过滤机制,在事件分发之前先过滤一部分事件

a.安装一个事件过滤器.

void QObject::installEventFilter(QObject *filterObj) 

延伸:一个是移除对应的事件过滤器

void QObject::removeEventFilter(QObject *obj)

b.重写QObject类的eventFilter函数。
virtual bool eventFilter(QObject *watched, QEvent *event);

4.event()函数主要用于事件的分发

通过QObject类event函数处理
virtual bool event(QEvent *event);
event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。如果传入的事件已被识别并且处理,返回 true,否则返回 false。如果返回值是 true,QApplication 会认为这个事件已经处理完毕,会继续处理事件队列中的下一事件;如果返回值是 false,QApplication 会尝试寻找这个事件的下一个处理函数。

参考:https://blog.csdn.net/baidu_16370559/article/details/128062334?csdn_share_tail=%7B"type"%3A"blog"%2C"rType"%3A"article"%2C"rId"%3A"128062334"%2C"source"%3A"baidu_16370559"%7D

元对象系统

Qt 的元对象系统叫 Mate-Object-System,提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。即使编译器不支持RTTI(RTTI的实现耗费了很大的时间和存储空间,这就会降低程序的性能),我们也能动态获取类型信息。

但是,元对象是基于三个条件的:

 1、该类必须继承自QObject类

 2、必须在类的私有声明区声明Q_OBJECT宏(在类定义的时候,如果没有指定public,
则默认为private,用来启用元对象功能,比如动态属性、信号和槽)。 

 3、 元对象编译器Meta-Object Compiler(moc)为 QObject的子类实现元对象 
特性提供必要的代码。

有了元对象系统后,我们就可以使用Qt的信号和槽了。

moc(Meta-Object Compiler)元对象预编译器。

moc读取一个c++头文件。如果它找到包含Q_OBJECT宏的一个或多个类声明,它会生成一个包含这些类的元对象代码的c++源文件,并且以moc_作为前缀。

信号和槽机制、运行时类型信息和动态属性系统需要元对象代码。

由moc生成的c++源文件必须编译并与类的实现联系起来。通常,moc不是手工调用的,而是由构建系统自动调用的。

 获取类关联的元对象的函数是:metaObject
 QMetaObject  *mtobj = QObject::metaObject()

QMetaObject 的className函数可在运行时返回类的名称字符串。
 

    QObject *btn = new QPushButton();
    QString str = btn->metaObject()->className();
    qDebug() << str;   // "QPushButton"

属性系统

QObject的 setProperty和property 用于通过属性名称动态设置和获取属性值。其实主要实现c++和qml交互。 关于通过属性实现c++和qml通讯关联的介绍参考:qt qml与c++_小飞侠hello的博客-CSDN博客
由于元对象系统的特点,这就保证了Qt属性系统是独立于编译器和平台的。不仅如此,我们还可以使用Q_PROPERTY()宏来定义编译期的静态属性。

作用就是:当一个类的成员变量或者成员函数用属性系统处理一下,它们就从C++内部中暴露出来,而且大家都认得。

Q_PROPERTY宏用来定义可通过元对象系统访问的属性,通过它定义的属性,可以在QML中访问、修改,也可以在属性变化时发射特定的信号。

Q_PROPERTY()宏定义一个返回值类型为type,名称为name的属性,用READ、WRITE关键字定义属性的读取、写入函数,还有其他的一些关键字定义属性的一些操作特性。属性的类型可以是QVarient支持的任何类型,也可以用户自定义类型。

READ:用于读取属性值,如果未指定成员变量(通过MEMBER ),则需要读取访问器函数。

WRITE:写访问器函数是可选的。用于设置属性值。它必须返回void,并且必须只接受一个参数,要么是属性的类型,要么是指向该类型的指针或引用。

MEMBER:如果未指定读取访问器函数,则需要成员变量关联。这使得给定的成员变量可读写,而无需创建读写访问器函数。如果需要控制变量访问,除了成员变量关联(但不是两者)之外,还可以使用读或写访问器函数。

RESET:复位功能是可选的。它用于将属性设置回其特定于上下文的默认值。

NOTIFY:通知信号是可选的。如果已定义,它应该指定该类中的一个现有信号,该信号在属性值更改时发出。成员变量的通知信号必须采用零个或一个参数,这些参数必须与属性的类型相同。参数将采用属性的新值。仅当属性确实发生更改时才应发出NOTIFY信号,以避免绑定在QML中被不必要地重新计算。

对象树

QObject类中存在一个私有变量QList用来存储这个类的子类们,当给一个对象指定一个父对象时,QList会将自己加入到父对象的children()列表中,也就是加入到QList变量中。

使用对象树的意义:

        在父对象调用完毕被调用析构函数的时候,该父对象的子对象们也会被析构,析构顺序和构造顺序相反:

        构造顺序:父对象->子对象

        析构顺序:子对象->父对象

这样的机制在GUI编程中非常有用,可以减少代码冗余,而不用去一个一个从堆区把它们delete掉。

1.每个继承自QObject类的对象通过它的对象链表(QObjectList)来管理子类对象,当用户创建一个子对象时,其对象链表相应更新子类对象信息,对象链表可通过children()获取。

2.当父对象析构的时候,其对象链表中的所有(子)对象也会被析构(即在对象的析构函数中会删除子对象),父对象会自动将其从父对象列表中删除。Qt 保证没有对象会被 delete 两次。开发中手动回收资源时建议使用deleteLater代替delete,因deleteLater多次是安全的,而delete多次是不安全的。

对象树所带来的问题:

        构造函数在创建对象时被调用,也就是说如果子对象优先于父对象被创建(调用构造函数),在析构的时候,对象树会进行两次delete操作,这时候程序会报错。因为在栈里是先进后出,即先调用构造函数,会后调用析构函数。

       延伸:如果在构造时设置父对象为 NULL,那么当前实例不会有父对象存在,Qt 也不会自动析构该实例,除非实例超出作用域导致析构函数被调用,或者用户在恰当时机使用 delete 操作符或者使用 deleteLater 方法。

_ENUMS宏

要导出的类定义了想在QML使用的枚举类型,可以使用Q_ENUMS宏将该枚举类型注册到元对象系统中。

Q_INVOKABLE宏

定义一个类的成员函数时使用Q_INVOKABLE宏来修饰,就可以让该方法被元对象系统调用
这个宏必须放在返回类型前面。

普通类成员函数是不能直接在qml使用。除非是声明为槽函数或者用Q_INVOKABLE声明函数。

    Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);
public slots:
     void setcolor();

所以:Q_INVOKABLE的作用有:
1.c++和qml混用。在c++类中用Q_INVOKABLE声明函数。这样可以在qml直接调用。

2.在跨线程编程中的使用。需要QMetaObject::invokeMethod()结合。将方法声明为Q_INVOKABLE,并且在另一线程中用invokeMethod唤起。

延伸:只有信号、槽以及被标记成Q_INVOKABLE的方法才能够被其它线程所触发调用。如果你不想通过跨线程的信号、槽这一方法来实现调用驻足在其他线程里的QObject方法。另一选择就是将方法声明为Q_INVOKABLE,并且在另一线程中用invokeMethod唤起。

说明:Q_INVOKABLE和槽差不多一样的作用

QMetaObject::invokeMethod()

可以使用QMetaObject::invokeMethod()调用QObject的某个注册到元对象系统中的方法。

bool QMetaObjcet:invokeMethod(
                        QObject* obj, 
                        const char* member,
                        Qt::ConnectionType type,
                        QGenericReturnArgument ret,
                        QGenericReturnArgument  vla0 = QGenericReturnArgument(0),
                        QGenericReturnArgument  vla1 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla2 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla3 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla4 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla5 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla6 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla7 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla8 = QGenericReturnArgument(),
                        QGenericReturnArgument  vla9 = QGenericReturnArgument());

返回值:返回true说明调用成功;返回false,要么是因为没有你说的那个方法,要么是参数类型不匹配;
obj:被调用对象的指针;
member:方法名字   必须是信号、槽,以及Qt元对象系统能识别的类型, 如果不是信号和槽,可以使用qRegisterMetaType()来注册数据类型。此外,使用Q_INVOKABLE来声明函数,也可以正确调用。
type:连接类型;invokeMethod为信号槽而生,你可以指定连接类型,如果被调用的对象和发起调用的线程是同一线程,那么可以使用Qt::DirectConnection、Qt::AutoConnection、Qt::QueuedConnection,如果被调用对象在另一个线程,那么建议使用Qt::QueuedConnection;
ret:接收返回值;
然后就是多达10个可以传递给被调用方法的参数;(看来信号槽的参数个数是有限制的,最好不要超过10个)

QString retVal;
QMetaObject::invokeMethod(
                            obj, 
                            "compute", 
                            Qt::DirectConnection,
                            Q_RETURN_ARG(QString, retVal),
                            Q_ARG(QString, "sqrt"),
                            Q_ARG(int, 42),
                            Q_ARG(double, 9.7)
                         );

参考:https://blog.csdn.net/baidu_16370559/article/details/105032962?csdn_share_tail=%7B"type"%3A"blog"%2C"rType"%3A"article"%2C"rId"%3A"105032962"%2C"source"%3A"baidu_16370559"%7D


 


 

你可能感兴趣的:(qt,qt,开发语言)