Qt的信号和槽机制

Qt的信号和槽机制

信号和槽机制是qt的优点之一 ,信号(signal)是某个对象发生某个事件之后向外广播出的一个信息,而槽( slot )是对此信号感兴趣的对象在这个信号产生时做出的动作,也就是调用函数完成某种操作。如果某个对象对此信号感兴趣,就需要使用connect函数将该信号与对应的槽函数绑定。比如,某个按钮被点击,向外广播出了按钮被点击的信号,而按钮点击的动作对应绑定槽函数的执行。

Qt5 的书写方式
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QPushButton buttonQuit( "点击按钮" );
    QObject::connect( &buttonQuit , &QPushButton::clicked,
    &app , &QApplication::quit);
    buttonQuit.show();
    return app.exec();
}
  • 创建好一个Qt工程 , 并在主函数文件中更改代码 ;
  • 运行程序,可在界面看到一个按钮,并且按钮的显示文本为*“点击按钮"*,单击按钮,窗口关闭。其中我们单击按钮的时候按钮对象向外广播出了一个click信号,这个信号被我们之前所调用的QObject类种的connect函数与QApplication对象中的quit槽函数绑定。故执行了quit函数,程序退出。
  • 槽函数的格式
    connect(sender, signal, receiver, slot);
  • 参数的含义:
    sender:发出信号的对象
    signal:发送对象发出的信号
    receiver:接收信号的对象
    slot:接收对象在接收到信号之后所需要调用的函数(槽函数
  • 信号与槽函数的传参问题
    槽函数的形参必须与信号函数的前半部分一致,即槽函数对应的参数可以在信号函数对应的参数位置找到。允许信号参数的个数多于槽函数信号的个数,但必须满足这个规则。
  • Qt4与Qt5中connect函数使用方式对比
    Qt4
    connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
    Qt5
    connect(&button, &QPushButton::clicked, &QApplication::quit2);
    对比
    首先,Qt5兼容Qt4的写法。
    其次,Qt4SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串,故在编译的时候并不会检查信号与槽是否对应,即我们无法在编译的时候知道信号与槽是否绑定成功,如果出错,只会在程序运行的时候出现。Qt5的写法在编译的时候就进行语法检查,防止出错,可以迅速的定位程序中的错误。
    结论:推荐Qt5的写法。
  • 自定义信号和槽
    Qt中除了系统定义的信号函数和槽函数之外,我们也可以自己设计信号和槽。
    我们以狗狗铲屎官类和哈士奇类为例子来说明自定义信号和槽机制。当狗狗铲屎官给哈士奇扔一个鸡腿过去之后,哈士奇接到鸡腿,即可美餐一顿。代码如下:
//--doghashiqi.h--
#include <QObject>
#include <QDebug>
class DogHashiqi : public QObject
{
    Q_OBJECT
public:
    DogHashiqi(const QString & name) :
        m_name(name)
    {
    }
 
    void eat( QString food )
    {
         qDebug() << m_name << "enjoy" << food << "nice" ;   
    }
private:
    QString m_name;
};
 
//------manchanshiguan.h---------
#include <QObject>
#include <QDebug>
class ManChanshiguan : public QObject
{
    Q_OBJECT
public:
    ManChanshiguan( QString name ):m_name(name)
    {
    
    }
    void feedDog( QString food )
    {
         qDebug() << m_name << "feed dog" ;
         emit timeToEat( food ) ;
    }
signals: 
     void timeToEat( QString  food ) ;
};
 
//------main.cpp------
#include <QCoreApplication>
#include "manchanshiguan.h"
#include "doghashiqi.h"
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    ManChanshiguan manChanshiguan( "Leng Wa" );
    DogHashiqi dogHashiqi( "Wang Zai" );
    QObject::connect( &manChanshiguan , &ManChanshiguan::timeToEat ,
                     &dogHashiqi , &DogHashiqi::eat );
    manChanshiguan.feedDog( "Chicken" );
    return app.exec();
}
  • ManChanshiguan 和 DogHashiqi 类中都继承于QObject类,这是信号和槽机制实现的基础;
  • QObject类(基类或者其派生类),都应在类定义的第一行写上Q_OBJECT,这个宏为我们的类提供信号和槽机制、国际化机制、QT提供的不基于C++ RTTI的反射能力;
  • DogHashiqi类中有一个eat函数和构造函数,其中构造函数用于给哈士奇初始化名字,eat即为槽函数,哈士奇可以用这个函数来接受铲屎官扔出的鸡腿信号并享受鸡腿;
  • ManChanshiguan类种有feedDog函数和信号函数声明timeToEat,feedDog是铲屎官喂狗的动作,而timeToEat即告诉哈士奇有鸡腿了。通过timeToEat函数中的参数告诉哈士奇我扔给你的是什么食物(信号与槽函数的参数传递),emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。用于告诉哈士奇该吃饭了(发出timeToEat信号);
  • 主函数中我们定义一个铲屎官 “Leng Wa” ,哈士奇 "Wang Cai " , “LengWa” 调用喂狗函数扔出一个鸡腿,哈士奇"Wang Cai"即可吃掉鸡腿;
  • 要点回顾
    发送者和接收者必须是QObject的子类(槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
    使用 signals 标记信号函数,信号是一个函数声明,返回 void;
    槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的限定;
    使用 emit 发送信号;
    使用QObject::connect()函数连接信号和槽;
    任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。
  • 扩展
    一个信号可以和多个槽相连
    如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
    多个信号可以连接到一个槽
    只要任意一个信号发出,这个槽就会被调用。
    一个信号可以连接到另外的一个信号
    当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
    槽可以被取消链接
    这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
    使用Lambda 表达式
    在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
    我们的代码可以写成下面这样:
connect( senderAddr , signal ,
         [=]()
         {
            //完成某种动作
         } ) ;
  • Lambda表达式
    在这里插入图片描述

  • 函数对象参数;
    [ ],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
    。没有使用任何函数对象参数;
    =, 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量);
    &, 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量);
    this , 函数体内可以使用Lambda所在类中的成员变量;
    a, 将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符;
    &a , 将a按引用进行传递;
    a, &b, 将a按值进行传递,b按引用进行传递;
    =,&a, &b , 除a和b按引用进行传递外,其他参数都按值进行传递;
    &, a, b , 除a和b按值进行传递外,其他参数都按引用进行传递。
    int m = 0, n = 0;
    [=] (int a) mutable { m = ++n + a; }(4);
    [&] (int a) { m = ++n + a; }(4);

    [=,&m] (int a) mutable { m = ++n + a; }(4);
    [&,m] (int a) mutable { m = ++n + a; }(4);
    
    [m,n] (int a) mutable { m = ++n + a; }(4);
    [&m,&n] (int a) { m = ++n + a; }(4);
    
  • 操作符重载函数参数;
    标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。

  • 可修改标示符;
    mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身);

  • 错误抛出标示符;
    exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)

  • 函数返回值;
    返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略;

  • 函数体;
    {},标识函数的实现,这部分不能省略,但函数体可以为空。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

百万攻城狮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值