• qt-C++笔记之信号与槽


    qt-C++笔记之信号与槽

    code review!

    本文抄自公众号:嵌入式小生


    在这里插入图片描述

    一.信号

    2.1.信号的发出

    由于某种条件到达可能引起了对象改变,其内部状态将发生改变,这时候对象就会发出信号。信号是公共访问函数,可以从任何地方发出,但是建议:【只从定义该信号的类及其子类发出信号】。

    Qt框架下,信号发出分为两种:

    1、【每个类预定义的信号】:这些信号何时发出可以通过查看官方文档获知。

    2、【自定义的信号】:这些信号的发出由开发人员自行定义。

    2.2.信号的处理

    当一个信号发出时,连接到它的槽函数通常会立即执行,就像一个普通函数调用一样。在这种情况下,信号和槽函数机制是完全独立于GUI事件循环的,也并不会干扰GUI的事件循环。emit语句之后的代码将在所有槽函数都返回之后才执行。如果使用排队连接(queued connections),情况略有不同,在这种情况下,emit关键字后面的代码将立即继续,槽函数将在后续执行。

    如果几个槽函数连接到同一个信号上,当信号发出时,这些槽函数将按照它们连接时的顺序依次执行【这一点很重要】。

    信号是由moc工具自动生成,不能在.cpp文件中实现,所以信号永远不能有返回类型(必须使用void关键字定义信号)。

    关于信号和槽参数的注意事项:经验表明,如果信号和槽函数不使用特殊类型,那么代码具有极强的可重用性。

    下表是使用connect()创建信号和槽函数连接时,可以指定5种不同的连接类型:

    序号类型含义
    1Qt::AutoConnection如果接收者生活在发出信号的线程中,Qt::DirectConnection被使用。否则,使用Qt::QueuedConnection。连接类型是在信号发出时确定。【这是Qt创建信号和槽函数时的默认连接方式】
    2Qt::DirectConnection当信号发出时,槽函数立即被调用。槽函数在发送信号的线程中执行。
    3Qt::QueuedConnection当控制返回到接收方线程的事件循环时,将调用槽函数。槽函数在接收方的线程中执行。
    4Qt::BlockingQueuedConnection与Qt::QueuedConnection相同,只是在槽函数返回之前线程会阻塞。如果接收方存在于发送信号的线程中,则不能使用此连接,否则应用程序将会死锁。
    5Qt::UniqueConnection这是一个标志,可以使用按位OR与上述的连接类型进行组合。当Qt::UniqueConnection被设置时,如果连接已经存在,QObject::connect()将失败(例如,如果相同的信号已经连接到同一对对象的相同槽位)。注:这个标志在Qt 4.6中引入。

    二.槽函数

    当一个连接到槽函数的信号被发射时,槽函数将被调用。槽函数是普通的C++函数,在实际开发中也可以正常调用;它们唯一的特点是:【信号可以与它们相连接】。

    由于槽是普通的成员函数,所以它们在直接调用时遵循普通的C++规则。但是,作为槽函数时,任何组件都可以通过信号连接从而调用它们。

    还可以将槽函数定义为虚拟的,这在开发中非常有用。

    与回调机制相比,信号和槽函数机制的速度稍微慢一些,这一点对于实际应用程序来说,这种差别并不显著。一般来说,发送一个连接到某些槽函数的信号,比直接调用非虚函数要慢大约10倍。这是定位连接对象、安全地遍历所有连接(即检查后续接收方在发射过程中没有被销毁)以及以函数调用增加的开销。虽然10个非虚函数调用听起来很多,但是它比new操作或delete操作的开销要小得多。一旦在后台执行一个需要new或delete的字符串、向量或列表操作,信号和槽函数的开销只占整个函数调用开销的很小一部分。在槽函数中执行系统调用时也是如此(或间接调用超过十个函数)。因此信号和槽函数机制的简单性和灵活性是值得的,这些开销在实际应用场景下甚至不会注意到。

    注意,当与基于Qt的应用程序一起编译时,定义为信号或槽的变量的第三方库可能会导致编译器出现警告和错误。要解决这个问题,使用#undef来定义出错的预处理器符号即可。

    2.1.带有默认参数的信号和槽函数

    信号和槽可以包含参数,参数可以有默认值。例如:QObject::destroyed():

    void destroyed(QObject* = nullptr);
    
    • 1

    当QObject被删除时,它会发出这个QObject::destroyed()信号。无论我们在哪里有一个悬空引用指向已删除的QObject,都希望捕捉到这个信号,这样就可以清除它。合适的槽参数可以是:

    void objectDestroyed(QObject* obj = nullptr);
    
    • 1

    2.2.使用QObject::connect()将信号连接到槽函数的三种方法

    2.2.1.第一种方法:使用函数指针
    connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
    
    • 1

    将QObject::connect()与函数指针一起使用有几个优点。它允许编译器检查信号的参数是否与槽的参数兼容。当然,编译器还可以隐式地转换参数。

    2.2.2.第二种方法:连接到C++11的lambda
    connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });
    
    • 1

    在这种情况下,我们在connect()调用中提供这个上下文。上下文对象提供关于应该在哪个线程中执行接收器的信息。
    当发送方或上下文被销毁时,lambda将断开连接。注意:当信号发出时,函数内部使用的所有对象依然是激活的。

    2.2.3.第三种方法:使用QObject::connect()以及信号和槽声明宏。

    在SIGNAL()和SLOT()宏中包含参数(如果参数有默认值)的规则是:传递给SIGNAL()宏的参数不能少于传递给SLOT()宏的参数。

    例如以下代码都是合法的:

    connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
    connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
    connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));
    
    • 1
    • 2
    • 3

    但是这种是非法的:

    connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));
    
    • 1

    因为槽函数期望的是一个信号不会发送的QObject。此连接将报告运行时错误。

    注意,使用这种方法时,在使用QObject::connect()关联信号和槽函数时,编译器不会自动检查信号和槽函数的参数之间是否匹配。

    综上:使用第一种方法 创建信号和槽 在开发中较为常用,也较为合适。

    2.3.信号和槽函数的一些高级用法

    当需要获取信号发送方的信息时,使用Qt提供QObject::sender()函数,该函数返回一个指向发送信号对象的指针。

    Lambda表达式是传递自定义参数到槽的一种方便方式:

    connect(action, &QAction::triggered, engine,[=]() { engine->processAction(action->text()); });
    
    • 1

    2.4.使用disconnect断开信号/槽连接

    disconnect()用于断开对象发送器中的信号与对象接收器中的方法的连接。如果连接成功断开,则返回true;否则返回false。

    当对信号/槽关联的两方中的任何一个对象进行销毁时,信号/槽连接将被移除。

    disconnect()有三种使用方法,如下示例所示:

    1、断开所有与对象相连的信号/槽:

    disconnect(myObject, nullptr, nullptr, nullptr);
    
    • 1

    相当于非静态重载函数:

    myObject->disconnect();
    
    • 1

    2、断开所有与特定信号相连的对象:

    disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr);
    
    • 1

    相当于非静态重载函数:

    myObject->disconnect(SIGNAL(mySignal()));
    
    • 1

    3、断开特定接收对象的连接:

    disconnect(myObject, nullptr, myReceiver, nullptr);
    
    • 1

    相当于非静态重载函数:

    myObject->disconnect(myReceiver);
    
    • 1

    nullptr可以用作通配符,分别表示“任何信号”、“任何接收对象”或“接收对象中的任何槽”。

    如下格式的使用示例:

    disconnect(发送对象,信号,接收对象,方法)
    
    • 1

    发送对象不会是nullptr。

    如果信号为nullptr,将断开接收对象和槽函数与所有信号的连接。否则只断开指定的信号。

    如果接收对象是nullptr,它断开所有关联该信号的连接。否则,只断开与接收对象的槽函数连接。

    如果方法是nullptr,它会断开任何连接到接收对象的连接。如果不是,只有命名为方法的槽函数连接将被断开。如果没有接收对象,方法必须为nullptr。即:

    disconnect(发送对象,信号,nullptrnullptr
    • 1

    三.使用Qt与第三方信号和槽函数

    当第三方库中也有信号/槽函数机制时,这时候又需要使用Qt的信号和槽函数机制。对于这种开发场景,Qt可以在同一个项目中使用这两种机制。需将下面一行添加到qmake项目(.pro)工程配置文件中:

    CONFIG += no_keywords
    该配置将告诉Qt不要定义moc关键字信号、槽函数和emit,因为这些名称将被第三方库使用(例如Boost)。如果要在使用no_keywords标志下继续使用Qt信号和槽机制,需将源文件中所有的Qt moc关键字替换为对应的Qt宏:Q_SIGNALS(或Q_SIGNAL)、Q_SLOT(或Q_SLOT)和Q_EMIT。

    四.SIGNAL()和SLOT()是Qt定义的两个宏

    SIGNAL()和SLOT()是Qt定义的两个宏,它们返回其参数的C语言风格的字符串(const char*)。因此,下面关联信号和槽的两个语句是等同的:

    connect(button,SIGNAL(clicke()),this,SLOT(showArea()));
    connect(button,"clicked()",this,"showArea()");
    
    • 1
    • 2

    五.Qt中QEvent和信号槽的区别——未完,没搞明白,搞明白就补充

    六.阿西拜编程——信号与槽部分PPT

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    七.通过函数名称将信号与槽关联

    在Qt Designer中提供了一个快速关联信号和槽函数的方式:使用『转到槽…』对话框创建选定控件之间的信号和槽函数关联。
    在这里插入图片描述

    在这里插入图片描述

    从上图可知,首先QObject和QWidget下的信号是公共的,QAbstractButton是按钮类独有的,Qt使用该类抽象出了按钮的一些公共操作,并已定义好具体的信号。当选中具体信号后,点击“确定”按钮,QtCreator会自动生成一个槽函数,例如,如果选择了clicked()这个信号,则会在代码中生成如下代码,在头文件生成代码如下:
    在这里插入图片描述

    本质上,QtCreator生成代码的成员函数的命名是能够被QMetaObject的自动连接工具识别的,在构建过程中,在uic生成的用于描述界面的C++文件中,会出现以下代码:
    在这里插入图片描述

    因此,通过connectSlotByName()就将名称和槽函数进行了关联,该函数会递归搜索给定对象的所有子对象,并将它们的匹配信号连接到遵循特定格式命名的槽函数,格式如下:

    void on_<object name>_<signal name>(<signal parameters>)
    
    • 1
    • :对象名称。
    • :信号名称。
    • :传入的参数。

    在实际使用过程中,只需要声明和实现一个槽函数,其名称遵循标准约定,就可以将一个控件对象的信号和具体槽函数关联,当然这个过程由QtCreator自动完成。

    Qt提供的信号和槽函数的自动连接机制,为开发小部件提供了标准命名约定和显式接口。通过提供实现给定界面的源代码,在设计用户界面时无需再编写代码就可以检查设计是否真正有效了。

    总结

    基于QtCreator,提供的信号和槽函数的自动连接机制在实际开发中,经常会被使用到,特别是在需要快速验证功能和需求的时候,这种方式比使用connect()关联信号和槽函数的开发效率要快很多。但是,如果界面中同类型控件较多的情况下,就需要注释清楚,不然不好区分具体控件下的功能。

    在实际项目开发中,对于自定义的信号,习惯使用connect()进行槽函数的关联。对于具体界面中控件,如果是Qt提供的标准控件,则可以使用信号和槽函数的自动连接机制,以加快开发效率。

    八.ChatGpt——信号与槽

    8.1.一些常见的信号与槽的用法

    Qt是一个强大的C++框架,它提供了多种形式的信号与槽机制,用于在应用程序中管理事件和数据流。以下是一些常见的信号与槽的用法:

    1. 基本信号与槽连接:

      QObject::connect(sender, SIGNAL(mySignal()), receiver, SLOT(mySlot()));
      
      • 1
    2. 信号与槽连接,传递参数:

      QObject::connect(sender, SIGNAL(mySignal(int, QString)), receiver, SLOT(mySlot(int, QString)));
      
      • 1
    3. 使用Lambda表达式连接信号与槽:

      QObject::connect(sender, &Sender::mySignal, receiver, [=](int value) {
          // 处理信号
      });
      
      • 1
      • 2
      • 3
    4. 断开信号与槽连接:

      QObject::disconnect(sender, SIGNAL(mySignal()), receiver, SLOT(mySlot()));
      
      • 1
    5. 使用Qt5新的连接语法:

      QObject::connect(sender, &Sender::mySignal, receiver, &Receiver::mySlot);
      
      • 1
    6. 使用Qt的QueuedConnection连接信号与槽,实现跨线程通信:

      QObject::connect(sender, &Sender::mySignal, receiver, &Receiver::mySlot, Qt::QueuedConnection);
      
      • 1
    7. 自定义信号:

      class MyObject : public QObject {
          Q_OBJECT
      
      public:
          void myFunction() {
              // 发射自定义信号
              emit mySignal();
          }
      
      signals:
          void mySignal();
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    8. 重载信号:

      class MyObject : public QObject {
          Q_OBJECT
      
      public slots:
          void mySlot(int value) {
              // 处理整数参数
          }
      
      signals:
          void mySignal(int value);
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    9. 信号连接到多个槽函数:

      QObject::connect(sender, SIGNAL(mySignal()), receiver1, SLOT(mySlot1()));
      QObject::connect(sender, SIGNAL(mySignal()), receiver2, SLOT(mySlot2()));
      
      • 1
      • 2
    10. 连接同一信号的多个槽函数,指定连接类型:

      QObject::connect(sender, SIGNAL(mySignal()), receiver1, SLOT(mySlot1()), Qt::DirectConnection);
      QObject::connect(sender, SIGNAL(mySignal()), receiver2, SLOT(mySlot2()), Qt::QueuedConnection);
      
      • 1
      • 2

    这些是Qt中信号与槽的常见用法示例,Qt的信号与槽机制非常灵活,可以适应多种不同的场景和需求。

    8.2.this指针

    在Qt中,通常可以使用this指针将当前对象(QObject的派生类)连接到信号和槽。以下是一些使用this指针的示例:

    1. 使用this指针将当前对象连接到信号和槽:
    QObject::connect(this, SIGNAL(mySignal()), this, SLOT(mySlot()));
    
    • 1

    这将连接当前对象的mySignal信号到当前对象的mySlot槽函数。

    1. 使用Lambda表达式连接信号和槽,其中this指针也可以使用:
    QObject::connect(this, &MyClass::mySignal, this, [this]() {
        // 使用this指针处理信号
    });
    
    • 1
    • 2
    • 3
    1. 自定义信号和槽函数,并在类的方法内连接:
    class MyClass : public QObject {
        Q_OBJECT
    
    public slots:
        void mySlot() {
            // 处理槽函数的逻辑
        }
    
    public:
        void connectSignalsAndSlots() {
            QObject::connect(this, SIGNAL(mySignal()), this, SLOT(mySlot()));
        }
    
    signals:
        void mySignal();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上述示例中,connectSignalsAndSlots方法连接了当前对象的mySignal信号到当前对象的mySlot槽函数。

    使用this指针连接信号与槽是非常常见的,特别是在一个类内部的方法中连接信号与槽,以确保连接正确的对象。但请注意,当断开信号与槽连接时,也需要使用相同的this指针。

    8.3.使用特定的函数命名约定可以自动将信号与槽关联

    在Qt C++中,使用特定的函数命名约定可以自动将信号与槽关联。这一约定通常用于与Qt Designer或Qt Creator的UI设计器一起工作,以便在UI元素发出信号时,自动生成槽函数。以下是关于这一方式的详细解释:

    1. 函数命名约定
      通常,函数命名约定的格式是:void on__();。这个函数名称中包括了以下部分: