QThread、moveToThread用法详述

news2024/11/15 13:23:57

1.吐槽

        QThread类提供了一种平台无关的方法对线程进行管理。但对于QThread类的熟练使用,即使是从事Qt开发多年的程序猿们,往往也会踩雷、入坑。总之:QThread类不好用、如果对该类理解不透,很容易导致程序崩溃。本人强烈建议:利用C++进行线程开发,请用STL中的std::thread,std::thread进行线程开发非常简单,不易出错。关于std::thread的使用 ,请参考如下链接:

  • 《C++11线程管理基础》。
  • 《c++11 thread类的简单使用》。
  • 《c++11仔细地将参数传递给线程std::thread》。
  • 《C++11向线程函数传递参数》。
  • 《C++11多线程thread参数传递问题》。
  • 《C++11中线程所有权转移分析》。

2.预备知识

2.1.Object对象和线程的密切关系

      Qt中所有QObject对象都和线程有密切关系,或者说所有QObject对象都位于某个线程对象、依附于某个线程对象。当某个QObject对象收到queued signal(通过connect以方式Qt::QueuedConnection连接的信号)或posted events(通过postEvent发送的异步事件)时,槽函数或事件处理函数将会在QObject对象依附的线程内执行。

       说明:如果一个QObject对象没有位于任何线程,即没有附属于任何线程,就是说QObject对象的thread()方法返回nullptr,或者QObject对象所在线程没有运行任何事件循环,则QObject对象不会收到queued signal和posted events。QObject对象和其依附的线程关系就类似地球上的人和国家的关系。地球上的人必须属于某个国家,即地球人必须有国籍;只有有国籍,该地球人才能享受到这个国家的待遇,如:社会保险、救济金,身份证等,当然也可能有些人没有任何国籍,那么该人就不会享受到任何国家的权利和应尽义务。

     QObject对象默认依附于创建它的线程内。一个QObject对象所在、所依附的线程对象可以通过QObject类的QObject::thread()方法来查询,如下为该方法:

QThread *QObject::thread() const

Qt官方对该方法的解释如下:

Returns the thread in which the object lives.

       即返回该对象依附的线程对象。可以通过QObject类的moveToThread方法来改变QObject类和线程的依附关系。

       所有对象依附的线程必须和它们的父对象依附的线程是同一个线程,因此:

  • 如果两个QObject对象分别依附于不同的线程,对它们之间setParent()函数,从而设置父子关系会失败。
  • 当将QObject对象通过moveToThread函数移动到另外一个线程,则QObject对象的所有孩子对象也会自动被移动到该线程。
  • 当QObject对象有父时,调用QObject对象的moveToThread函数会失败。
  • QThread::run()函数会产生一个新的子线程(为便于后文描述,称为子线程B),如果 QObject对象是在QThread::run()函数内部创建的,则 QObject对象依附于B,则在B线程中创建的QObject对象不会变为QThread的孩子,因为QThread对象不是B线程创建的。也就是说在QThread::run()内部创建的QObject对象和QThread对象依附于不同的线程对象,前者依附于B线程,后者依附于创建它的线程。

说明:QObject对象的成员变量不会自动变为QObject对象的孩子。父子关系的建立必须通过下述方法中某一种形成:

  • 通过将QObject对象的指针传给孩子的构造函数。
  • 通过将QObject对象作为setParent函数的参数传入。

        没用通过上述方法中某一种建立父子关系,则调用moveToThread函数后,对象的成员变量依然依附于原来创建该对象的线程。

2.2.改变QObject类对象的线程依附关系

QObject类有moveToThread函数,如下:

void QObject::moveToThread(QThread *targetThread)

      该方法改变QObject类对象及该对象下的所有子对象的线程依附关系到参数targetThread线程对象上。比如:如果a是一个QObject类对象,b是a的孩子对象,a对象是在线程th1中创建的,则调用如下代码后:

QThread *c = ... ;// c是另外一个线程对象
a.moveToThread(c);

      a及a的孩子b就不再依附于th1,转而依附于线程c了。此后,a中的事件处理将会在线程c中进行,而不再是在原来的th1线程中进行。

     说明:如果QObject类对象有父,则它不能被移动,上例中对b调用如下代码,则不会改变b依附的线程到c上,b中的事件处理依然在线程th1中进行,而不是在c线程中进行。:

QThread *c = ... ;// c是另外一个线程对象
b.moveToThread(c);

       利用如下代码:

QApplication::instance();

可以查询指向当前应用程序的指针;然后利用如下代码查询当前应用程序依附的线程对象:

QApplication::thread();

如下代码:

 myObject->moveToThread(QApplication::instance()->thread());

可以将QObject类型的myObject对象依附的线程移动到当前应用程序所在的线程上来。如下:

 myObject->moveToThread(nullptr);

       如果moveToThread的参数targetThread为nullptr,则调用moveToThread函数的对象myObject及其子对象的所有事件都会停止,因为myObject不再依附任何线程对象。

       上述对myObject以非nullptr参数调用moveToThread函数,则myObject对象上的所有正处于激活状态的定时器(QTimer、QBaseTimer等)都会被重置。myObject对象关联的定时器首先会在当前线程(创建myObject对象的线程)停止,然后以相同的定时器间隔在moveToThread的参数targetThread表示的线程被重新启动。如果一直不间断的在线程之间调用moveToThread,则定时器事件会无限期地被延迟。

        当改变对象的线程依附关系之前, QEvent::ThreadChange事件会发送到该对象上,你能够处理该事件,以执行某些特定的操作。注意:改变对象的线程依附关系后,所有发送到该对象上的事件,将都会在targetThread表示的线程内被处理,而不再是在该对象被创建的线程内被处理。如果moveToThread的参数targetThread为nullptr,则调用moveToThread的对象及其所有孩子的所有事件都不会被处理、激发,因为没有任何线程和该对象及其孩子关联。

        注意:moveToThread函数不是线程安全的;该函数仅仅能从当前线程“压入”一个对象到另外一个线程,而不能从任意一个线程“取出”一个对象到当前线程。不过这个规则有个例外:那就是不依附任何线程的对象能被“取出”到当前线程。

3.QThread类

      QThread类提供了一种平台无关的方法对线程进行管理,QThread类能在程序内对一个线程进行控制。QThread类的run方法被调用后,就会生成子线程并且该子线程就开始执行。默认情况下,QThread类的run方法通过调用exec()函数开启事件循环,且在线程内部运行事件循环。你可以构建一个工作者对象worker objects,然后通过QObject::moveToThread()方法将工作者对象worker objects移动到某个线程上,即前面说的依附到某个线程对象上。如下代码:

 class Worker : public QObject
  {
      Q_OBJECT

  public slots:
      void doWork(const QString &parameter) {
          QString result;
          /* ... here is the expensive or blocking operation ... */
          emit resultReady(result);
      }

  signals:
      void resultReady(const QString &result);
  };

  class Controller : public QObject
  {
      Q_OBJECT
      QThread workerThread;
  public:
      Controller() {
          Worker *worker = new Worker;
          worker->moveToThread(&workerThread);
          connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
          connect(this, &Controller::operate, worker, &Worker::doWork);
          connect(worker, &Worker::resultReady, this, &Controller::handleResults);
          workerThread.start();
      }
      ~Controller() {
          workerThread.quit();
          workerThread.wait();
      }
  public slots:
      void handleResults(const QString &);
  signals:
      void operate(const QString &);
  };

      在上面的代码中,因为Worker类对象worker通过moveToThread函数将其依附的线程从创建它的线程移动到了workerThread线程,所以Worker类对象worker的槽函数doWork将会在workerThread线程中执行,而不再是在原来创建worker对象的线程执行。尽管如此,你依然可以自由地连接Worker类对象worker的槽函数到任何对象的任何信号。得益于Qt的 

queued connections技术(即通过connect以Qt::QueuedConnection为参数连接信号)的使用,在不同的线程之间连接信号和槽是安全的。

        另外一种使代码运行在单一子线程的方法是子类化QThread类,然后重新实现run()方法,如下:

  class WorkerThread : public QThread
  {
      Q_OBJECT
      void run() override {
          QString result;
          /* ... here is the expensive or blocking operation ... */
          emit resultReady(result);
      }
  signals:
      void resultReady(const QString &s);
  };

  void MyObject::startWorkInAThread()
  {
      WorkerThread *workerThread = new WorkerThread(this);
      connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
      connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
      workerThread->start();
  }

       在这个例子中,当run函数执行完返回时,线程就会退出,如果不调用exec()函数,则不会产生任何事件循环。非常重要的一点是:QThread实例附属、依附于生成该QThread实例的线程,而不是附属、依附于通过调用run()函数生成的子线程。这意味着线程所有的队列信号槽函数和QMetaObject类的invokeMethod函数的执行都在创建QThread实例的线程中执行,而不是在调用run()函数生成的子线程中执行,因此,如果开发者想槽函数的执行在新生成的子线程中执行,必须用前文说到的工作对象(worker-object)的技术方法;新的槽函数不应该直接在QThread子类中实现。

       不同于槽函数和QMetaObject类的invokeMethod函数的执行,那些在QThread对象上直接被调用的函数,将会在调用该函数的线程中执行。当子类化QThread时,始终要记住一点:当构造QThread时,构造函数(为便于后文描述,暂且称构造函数为construct()  )将会在原来的线程(为便于后文描述,暂且称该线程为A)中执行,而一旦QThread对象构造完成且运行QThread对象的run()函数,则run函数是运行在新生成的子线程(为便于后文描述,暂且称该线程为B)而不是运行在A线程。如果成员变量从construct() 和run()这两个函数中都被访问,这意味着成员变量被两个不同线程A和B访问,请确保这样跨线程的访问的安全性,如:是否要线程同步、加线程锁、互斥访问等。

总结:

  1. 当在A线程中创建QThread对象,则被创建的QThread对象属于A线程,即依附、附属于A线程。
  2. 当在1中创建的QThread对象的run函数运行(通过调用QThread类的start()方法)时,会产生一个新的子线程B。A和B是属于不同的线程。

如下代码打印出了A、B线程的id,可以明显看出A、B属于不同线程:

myThread.cpp:

#include "myThread.h"
#include<QDebug>
CMyThread::CMyThread(QObject* parent/* = nullptr*/)
	:QThread(parent)
{
	qDebug() <<"A:" <<  this->currentThreadId();
}
CMyThread::~CMyThread()
{

}

void CMyThread::run()
{
	qDebug() << "B:" << this->currentThreadId();
}

myThread.h:

#pragma once
#include <QThread>
class CMyThread :
    public QThread
{
public:
    CMyThread(QObject* parent = nullptr);
    ~CMyThread();

private:
    virtual void run() override;
 
};

main.cpp:

#include <QtCore/QCoreApplication>
#include "myThread.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    CMyThread myThread;
    myThread.start();
 
    return a.exec();
}

 运行结果如下:

 4.QThread类常见踩雷、入坑、崩溃汇总说明

      第3节部分提到了很多需要注意的点,尤其是标红的部分。如果对这些理解不透,往往会导致利用QThread类进行编程时,会出现各种崩溃,即使是从事Qt多年的程序猿也感觉到很难排查,非常棘手,这也是开篇我吐槽说在多线程编程中,尽量用STL的std::thread进行多线程编程而最好不用QThread类进行多线程编程。但既然是从事Qt的猿,本着钻研的科学精神,我们还是理解透为好。下面举例说明QThread类的坑:

myObject.h代码如下:

#pragma once
#include <QObject>
class CMyObject :
    public QObject
{
    Q_OBJECT
public:
    CMyObject(QObject* parent = nullptr){};
    ~CMyObject(){};
 
};

myThread.h代码如下:

#pragma once
#include <QThread>
#include "myObject.h"
class CMyThread :
    public QThread
{
public:
    CMyThread(QObject* parent = nullptr);
    ~CMyThread();

public:
    
private:
    virtual void run() override;
private:
    CMyObject* m_pMyObj{nullptr};
 
};

myThread.cpp代码如下:

#include "myThread.h"
#include<QDebug>
#include <iostream>
#include <iomanip>
#include<QCoreApplication>
CMyThread::CMyThread(QObject* parent/* = nullptr*/)
	:QThread(parent)
{
	m_pMyObj = new CMyObject(this);

	// 输出创建CMyThread对象的线程id,也就CMyThread对象依附、附属的线程id
	qDebug() <<"AThreadId:" <<  this->currentThreadId();

	// 输出主线程id
	qDebug() << "MainThreadId:" << qApp->thread()->currentThreadId();

	// 设置16进制输出
	std::cout.setf(std::ios_base::hex, std::ios_base::basefield);
	std::cout.setf(std::ios_base::showbase);

	// 以16进制格式输出线程句柄
	std::cout << "AThreadHandle:" << (qint64)this << "\r\n";
}
CMyThread::~CMyThread()
{

}

void CMyThread::run()
{
	// 输出新生成的子线程id
	qDebug() << "BThreadId:" << this->currentThreadId();
	auto pObj = new QObject(m_pMyObj);
	//m_pMyObj->setParent(pObj);
}

main.cpp代码如下:

#include <QtCore/QCoreApplication>
#include "myThread.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    CMyThread myThread;
    myThread.start();
 
    return a.exec();
}

运行上述代码,则出现如下警告:

 上面是控制台程序,如果换成QWidget的程序,则会弹出如下中断框:

错误原因分析:

         调用QThread类start()后,QThread类的run()就会开始运行,且run函数会产生一个新的子线程(暂且称为B线程),从上面打印的可以看到构造QThread类对象的线程id为:0x5434(暂且称为A线程),构造出的QThread类对象的句柄为:0xf47dcff908,而B线程id为: 0x63b0,可以看到A、B是两个不同的线程。上面打印出了主线程id,即main函数所在线程id,可以看到这里的A线程其实就是主线程。myThread.cpp中第33行以m_pMyObj为父构造pObj对象,根据前面的讲解,则pObj属于线程B,而m_pMyObj在是CMyThread类的构造函数中创建,则m_pMyObj属于线程A。同样地,如果将第34行注释取消,第34行也会崩溃。因为第33、34行违背了前面讲解的:“所有对象依附的线程必须和它们的父对象依附的线程是同一个线程”原则,所以会崩溃。所以在利用QThread进行多线程编程时,牢记2个原则:

  • 不能在run函数中构造一个对象,其父是非run函数中构造出的对象。
  • 不能在非run函数中构造一个对象,其父是run函数中构造出的对象。

5.线程管理

        当QThread启动时,会通过发射如下信号通知调用方:

[signal] void QThread::started()

        当QThread结束时,会通过发射如下信号通知调用方:

[signal] void QThread::finished()

        可通过如下函数查询线程是否执行完成:

bool QThread::isFinished() const

        可通过如下函数查询线程是否在运行:

bool QThread::isRunning() const

       可以通过调用如下函数使线程停止:

void QThread::exit(int returnCode = 0)
[slot] void QThread::quit()

      在某些情况下,可以通过terminate函数,强力退出、杀死线程,尽管如此,不建议利用terminate函数,使用terminate强力退出线程是危险的,提倡线程优雅退出。

slot] void QThread::terminate()

       为了对Qt 4.8版本及以前版本的兼容,可以通过连接QThread类对象的finished信号到QObject::deleteLater()槽函数,从而实现当QThread类对象表示的线程执行完后,对其解构、删除。利用如下函数,实现对线程阻塞,直到其它线程执行完或者指定的超时时间到。

bool QThread::wait(unsigned long time = ULONG_MAX)

         QThread类提供了一些静态的、平台无关的sleep函数:

[static] void QThread::sleep(unsigned long secs)
[static] void QThread::msleep(unsigned long msecs)
[static] void QThread::usleep(unsigned long usecs)

分别以秒、毫秒、微秒来睡眠、阻塞线程指定时间。

         因为Qt是基于事件驱动的,所以大体来说, wait() 和 the sleep()是不需要用到的。考虑通过捕获finished()信号来替代wait()函数;用QTimer来替代sleep函数的调用。静态函数currentThreadId() 和 currentThread()返回当前执行线程的标识符,前者返回一个平台指定的线程id,后者返回指向QThread的指针。

        为了给线程一个名称(线程名称在某些情况需要用到,如:在linux中通过ps -l命令获取线程信息时),可以在线程启动之前,通过setObjectName()函数给QThread类对象设置名称。如果没有为QThread类对象设置名称,则QThread类对象的名称默认为QThread类对象运行时的类名。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/132118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(函数介绍)puts()函数

功能介绍 1. puts()函数用来向标准输出设备屏幕输出字符串并换行。 2. 函数的参数就是一个起始的地址&#xff0c;然后就从这个地址开始一直输出字符串&#xff0c;直到碰到\0就停止&#xff0c;然后这个\0是不进行输出的&#xff0c;是不能够算在里面的。与此同时&#xff…

十、字节缓冲流、字符流、转换流、对象操作流、对象序列化流

字节缓冲流 构造方法 字节缓冲流介绍 BufferedOutputStream&#xff1a;该类实现缓冲输出流.通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用BufferedInputStream&#xff1a;创建BufferedInputStream将创建一个内部缓冲区数…

2022年为什么要学习C语言?

为什么学习c语言 为什么学C语言逻辑&#xff1f; 为什么要学习C语言&#xff1f; 学习C语言的主要理由有以下几点&#xff1a; C语言可以作为学习计算机程序设计语言的入门语言&#xff1b; C语言是编写操作系统的首选语言&#xff0c;与计算机硬件打交道时灵巧且高效&…

labelImag安装与使用及构造数据集

在做目标检测任务时&#xff0c;需要进行标注&#xff0c;选择了LabelImg作为标注工具&#xff0c;下面是安装及使用过程。 我们使用Anconda的虚拟环境进行安装&#xff0c;激活环境后&#xff0c;执行&#xff1a; pip install labelimg -i https://pypi.tuna.tsinghua.edu.c…

代码随想录算法训练营第四天 java : 24. 两两交换链表中的节点 ,19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交,142环形链表II

文章目录Leetcode 24. 两两交换链表中的节点题目链接本题思路需要注意的点AC 代码Leetcode 19.删除链表的倒数第N个节点题目链接需要注意的点AC代码Leetcode面试题 02.07. 链表相交题目链接这个略了Leetcode 142环形链表II题目链接难点:AC代码今日收获**一朵玫瑰正马不停蹄地成…

【Linux】Linux下基本指令(三)

作者&#xff1a;一个喜欢猫咪的的程序员 专栏&#xff1a;《Linux》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1. Linux基本指令&#xff1a;&#xff08;续&#xff09; 1.1zip指令和u…

极智编程 | C++模板函数

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多笔记分享 大家好&#xff0c;我是极智视界&#xff0c;本文介绍一下 C模板函数。 模板函数是 C 中一种特殊的函数&#xff0c;它的类型参数列表用尖括号 <> 括起来&#xff0c;放在函数名的后面。使用模板函数&a…

Go 并发

来自 《Go 语言从入门到实战》 的并发章节学习笔记&#xff0c;欢迎阅读斧正&#xff0c;感觉该专栏整体来说对有些后端编程经验的来说比无后端编程经验的人更友好。。 Thread VS Groutine 创建时默认 Stack 大小&#xff1a;前者默认 1M&#xff0c;Groutint 的 Stack 初始化…

C语言可变参数与内存管理

有时&#xff0c;您可能会碰到这样的情况&#xff0c;您希望函数带有可变数量的参数&#xff0c;而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案&#xff0c;它允许您定义一个函数&#xff0c;能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定…

LeetCode题解 二叉树(八):404 左叶子之和;513 找树左下角的值;112 路径总和;113 路径总和II

二叉树 404 左叶子之和 easy 左叶子结点也好判断&#xff0c;若某结点属于左结点&#xff0c;且无子树&#xff0c;就是左叶子结点 即也如此&#xff0c;所以如果要判断&#xff0c;必然要从父结点下手&#xff0c;涉及到三层结点的处理 如果要使用递归法&#xff0c;要使用…

(二十三)大白话数据库服务器上的RAID存储架构的电池充放电原理

文章目录 1、RAID卡的缓存2、RAID卡的缓存里的数据会突然丢失怎么办?3、锂电池存在性能衰减问题1、RAID卡的缓存 服务器使用多块磁盘组成的RAID阵列的时候,一般会有一个RAID卡,这个RAID卡是带有一个缓存的,这个缓存不是直接用我们的服务器的主内存的那种模式,他是一种跟内…

网络静态路由综合实验

1.首先分配ip,配置ip和环回 [Huawei]sysname R1 [R1]interface LoopBack 0 [R1-LoopBack0]ip add 192.168.1.33 28 [R1-LoopBack0]q [R1]int l 1 [R1-LoopBack1]ip add 192.168.1.49 28 [R1-LoopBack1]q [R1]int g 0/0/0 [R1-GigabitEthernet0/0/0]ip add 192.168.1.1 30 [R1-…

JVM与Java体系结构

目录 前言 架构师每天都在思考什么&#xff1f; Java vs C Java生态圈 字节码 多语言混合编程 虚拟机与Java虚拟机 虚拟机 Java虚拟机 JVM的位置 JVM整体结构 Java代码执行流程 JVM的架构模型 举例 字节码反编译 总结 栈 JVM生命周期 虚拟机的启动 虚拟机的…

时间从来不语,确回答了所有问题——我的2022年终总结

趁着没阳&#xff0c;趁着电脑还能开机&#xff0c;趁着还能写&#xff0c;赶紧小结过去这一年。没有别的感觉&#xff0c;就是感觉太快&#xff0c;时间太过匆匆.....最大的感触是两个字“变化”&#xff0c;如果非要说四个字是“变化太快”&#xff0c;就如当下的yi情政策&am…

多线程_进阶

文章目录线程通信概念使用方式案例单例模式阻塞式队列线程池常见的锁策略乐观锁 悲观锁CASCAS存在的问题:ABA问题读写锁自旋锁公平锁 非公平锁非公平锁公平锁synchronizedjvm对synchronized的优化:锁升级synchronized的其他优化Lock体系synchronized vs lock独占锁vs共享锁独占…

Arch/Manjaro换源+安装常用的软件+安装显卡驱动

本文将教你&#xff1a;换源安装显卡驱动&#xff0c;安装常用软件例如腾讯会议&#xff0c;QQ&#xff0c;WPS 一起交流Linux知识&#xff0c;欢迎加入Skype群&#xff1a; Join conversationhttps://join.skype.com/q6wrF3d6Usni pacman换清华源 首先安装vim&#xff0c;用来…

(二十四)大白话RAID锂电池充放电导致的MySQL数据库性能抖动的优化

案例实战:RAID锂电池充放电导致的MySQL数据库性能抖动的优化 文章目录 1、磁盘故障怎么保障数据不丢失?2、线上MySQL数据库的性能定期抖动的原因1、磁盘故障怎么保障数据不丢失? 前面经过了几天的生产经验的一些铺垫,包括MySQL磁盘读写的机制,Linux存储系统的原理,RAID磁…

垃圾佬图拉丁装机

理论知识 缩线程 amd搞了个推土机架构 两个核心公用一个浮点运算单元&#xff0c;因为浮点运算只占百分之二十。 浮点运算应该交给更适合的gpu去做 好的对比 RDP 微软的RDP本身就定位是一个远程登录和维护windows系的工具&#xff0c;它为什么要支持管理别的系统&#xff…

【mybatis generator实战】 1.crud 2.计数 3.自定义复杂mapper代码组织

1.计数 2.CRUD 增 注意&#xff1a; insert&#xff1a;一个必须全部有值。 insertSelective是&#xff1a;部分有值就行&#xff0c;用的较多。 有疑问可以看源码&#xff0c;发现xxxSelective就是拼接了一些参数。 删 改 注意&#xff1a; 4个更新方法&#xff1a; …

QML学习笔记【06】:QML与C++交互

1 QML端直接调用C端变量及函数 1、 创建继承自QObject的C类&#xff0c;对象必须继承自QObject才能在QML被使用和访问 2、在类定义中使用Q_PROPERTY导出成员的READ、WRITE、NOTIFY接口&#xff0c;这样类中的成员变量就可以在QML调用和修改了&#xff0c;同时变量被修改后也会…