[QT入门篇]信号槽机制

一、信号与槽的引入

信号与槽(Signal & Slot)是 Qt 编程的基础。信号槽,实际是观察者模式 (发布 - 订阅模式)。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。当信号发出时,被连接的槽函数会自动被回调。类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。

信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候Qt 对应的窗口类会发出某个信号,以此对用户的挑选做出反应。

信号的本质就是事件:

1.按钮单击、双击

2.窗口刷新

3.鼠标移动、鼠标按下、鼠标释放

4.键盘输入....

对某一个窗口进行操作,该窗口就可以捕捉到这些被触发的事件。对于使用者来说触发了一个事件我们就可以得到 Qt 框架给我们发出的某个特定信号。信号的呈现形式就是函数, 也就是说某个事件产生, Qt 框架就会调用某个对应的信号函数, 通知使用者。在QT中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测。

槽(Slot)是对信号响应的函数。槽是一个函数,可以定义在类的任何部分(public、 private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行

信号与槽两者本身是相对独立的个体,没有任何联系,在 Qt 中需要使用 QOjbect类中的 connect 函数进二者的关联。这是手动关联的,也可以通过ui设计界面自动关联。

QMetaObject::Connection QObject::connect(
        const QObject *sender, PointerToMemberFunction signal, 
        const QObject *receiver, PointerToMemberFunction method, 
        Qt::ConnectionType type = Qt::AutoConnection);
参数:
  - sender:   发出信号的对象
  - signal:   属于sender对象, 信号是一个函数, 这个参数的类型是函数
              指针, 信号函数地址
  - receiver: 信号接收者
  - method:   属于receiver对象, 当检测到sender发出了signal信号, 
              receiver对象调用method方法,信号发出之后的处理动作
 
//参数 signal 和 method 都是函数地址
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);

//另外一种写法

connect(A,SIGNAL(B),C,SLOT(D));

当对象A发出B信号时候,就会触发对象C的槽函数D

使用connect()进行信号槽连接的注意事项:

1.connect函数相对于做了信号处理动作的注册
2.调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的 槽函数 也不会被调用
3.槽函数本质是一个回调函数, 调用是在信号产生之后, 调用是Qt框架来执行的
4.connect中的sender和recever两个指针必须被实例化, 否则conenct不会成功

connect()操作一般写在窗口的构造函数中, 相当于在事件产生之前在qt框架中先进行注册, 这样在程序运行过程中假设产生了按钮的点击事件, 框架就会调用信号接收者对象对应的槽函数了, 如果信号不产生, 槽函数也就一直不会被调用。

二、信号/槽使用

在 Qt 提供的标准类中可以对用户触发的某些特定事件进行检测,当用户做了这些操作之后,事件被触发类的内部就会产生对应的信号,这些信号都是 Qt 类内部自带的,因此称之为标准信号。

在 Qt 的很多类内部提供了很多功能函数,并且这些函数也可以作为触发的信号的处理动作,该函数在 Qt 中称之为标准槽函数。

两者之间通过connect()函数连接。

//功能:点击按钮窗口关闭

// 单击按钮发出的信号
[signal] void QAbstractButton::clicked(bool checked = false)
// 关闭窗口的槽函数
[slot] bool QWidget::close();

//连接
// 单击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);


如果想要在QT类中自定义信号槽, 需要满足以下条件:

1.新的子类必须从QObject类或者是QObject子类进行派生
2.在定义类的头文件中加入 Q_OBJECT 宏

在 Qt 中信号的本质是事件,但是在框架中也是以函数的形式存在的,该函数只有声明,没有定义。当自定义信号对应的事件产生之后,将这个信号发射出去即可,其实就是调用一下这个信号函数。

自定义信号的要求:

1.信号是类的成员函数
2.返回值必须是 void 类型
3.参数可以随意指定, 信号也支持重载
4.信号需要使用 signals 关键字进行声明
5.信号函数只需要声明, 不需要实现
6.在程序中发射自定义信号: 发送信号的本质就是调用信号函数,在信号函数前加关键字: emit, 但是可以省略不写,(emit == #define emit)

// Qt中的类想要使用信号槽机制必须要从QObject类派生,直接或间接派生都可以
class Test : public QObject
{
    Q_OBJECT
signals:
    void testsignal();
    void testsignal(int a);
};

在信号中的参数的作用是数据传递, 谁调用信号函数谁就指定实参,实参最终会被传递给槽函数。在调用的时候传递参数进去,进行信号的发送。

槽函数是信号的处理动作,在 Qt 中槽函数可以作为普通的成员函数来使用。

自定义槽的要求:

1.返回值必须是 void 类型

2.槽也支持重载

3.槽函数需要指定多少个参数, 需要看连接的信号的参数个数,槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数.

4.槽函数的参数应该和对应的信号的参数个数,从左到右类型依次对应,信号的参数可以大于等于槽函数的参数个数 

5.Qt 中的槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数)

6.槽函数可以使用关键字进行声明: slots

三、信号槽使用拓展

一个信号可以连接多个槽函数,发送一个信号有多个处理动作,但需要写多个 connect()连接


在qt5中,槽函数的执行顺序和 connect 函数的调用顺序有关


信号的接收者可以是一个对象,也可以是多个对象


一个槽函数可以连接多个信号,多个不同的信号,处理动作是相同的,需要写多个 connect()连接

信号可以连接信号

信号接收者可以不处理接收的信号,继续发射新的信号,这相当于传递了数据,并没有对数据进行处理。

信号槽可以使用disconnect函数断开。

(qt4)信号槽连接方式:信号槽函数通过宏 SIGNAL 和 SLOT 转换为字符串类型。因为信号槽函数的转换是通过宏来进行转换的,因此传递到宏函数内部的数据不会被进行检测, 如果使用者传错了数据,编译器也不会报错,实际上信号槽的连接已经不对了,只有在程序运行起来之后才能发现问题,而且问题不容易被定位。

而用connect方式连接,信号和槽函数第2,4个参数传递的是地址, 编译器在编译过程中会对数据的正确性进行检测。

// Qt4的信号槽连接方式
[static] QMetaObject::Connection QObject::connect(
    const QObject *sender, const char *signal, 
    const QObject *receiver, const char *method, 
    Qt::ConnectionType type = Qt::AutoConnection);

connect(const QObject *sender,SIGNAL(信号函数名(参数1, 参数2, ...)),
        const QObject *receiver,SLOT(槽函数名(参数1, 参数2, ...)));

但是,如果对于重装函数的槽函数来说在使用 Qt5进行槽连接时,由于传递的是函数的地址,信号和槽都是通过函数名去关联函数的地址, 但是这个同名函数对应两块不同的地址, 一个带参, 一个不带参, 因此编译器就不知道去关联哪块地址, 所以如果在这种时候通过以上方式进行信号槽连接, 编译器就会报错

解决办法:可以通过定义函数指针的方式指定出函数的具体参数,这样就可以确定函数的具体地址了。定义函数指针指向重载的某个信号或者槽函数,在 connect()函数中将函数指针名字作为实参就可以了。

// 函数指针
void (Me::*func1)(QString) = &Me::eat;    // func1指向带参的信号
void (Me::*func2)() = &Me::hungury;    // func2指向不带参的槽函数


// 定义函数指针指向重载的某一个具体的信号地址
void (Me::*mysignal)(QString) = &Me::eat;
// 定义函数指针指向重载的某一个具体的槽函数地址
void (Me::*myslot)(QString) = &Me::hungury;
// 使用定义的函数指针完成信号槽的连接
connect(&m, mysignal, &m, myslot);

小结:

1.Qt4的信号槽连接方式使用了宏函数, 宏函数对用户传递的信号槽不会做错误检测, 容易出bug
2.Qt5的信号槽连接方式, 传递的是信号槽函数的地址, 编译器会做错误检测, 减少了bug的产生
3.当信号槽函数被重载之后, Qt4的信号槽连接方式不受影响
4.当信号槽函数被重载之后, Qt5中需要给被重载的信号或者槽定义函数指针

四、Lambda表达式

Lambda 表达式是一个匿名函数, 语法格式如下:

[capture](params) opt -> ret {body;};
         - capture: 捕获列表
         - params: 参数列表
         - opt: 函数选项
         - ret: 返回值类型
         - body: 函数体

1.捕获列表:捕获一定范围内的变量

[ ] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量,并作为副本在函数体内使用 (按值捕获),拷贝的副本在匿名函数体内部是只读的,若需要操作变量,可在opt中传递mutable
[=, &foo] - 按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量,同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量,同时不捕获其他变量
[this] - 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限

2.参数列表:和普通函数的参数列表一样

3.opt 选项 –> 可以省略
mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw ();

4.返回值类型:
标识函数返回值的类型,这部分可以省略

5.函数体:
函数的实现

因为 Lambda 表达式是一个匿名函数,因此是没有函数声明的,直接在程序中进行代码的定义即可,但是如果只定义匿名函数在程序执行过程中是不会被调用的。

// 匿名函数的定义, 程序执行这个匿名函数是不会被调用的
[](){
    qDebug() << "我是一个lambda表达式...";
};

// 匿名函数的定义+调用(在花括号后跟一个小括号()):
int ret = [](int a) -> int
{
    return a+1;
}(100);  // 100是传递给匿名函数的参数
  //匿名函数发送信号
     connect(ui->hurry,&QPushButton::clicked,this,[=](){
         emit mygirl->hungry(); // 发送信号
         emit mygirl->hungry("泡面"); // 发送信号
     });

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
QT信号机制QT框架一个非常重要的特性,用于对象间的通信。信号机制的基本思想是:一个对象发出一个信号,另一个对象通过连接到这个信号(slot)来处理这个信号。 在QT信号都是特殊的函数。信号是一个对象的特殊函数,它定义了一种特殊的行为,当这个对象的某些状态发生改变时,它会自动发出信号也是一个对象的特殊函数,它定义了一种特殊的行为,当某个信号被发出时,与这个信号相连接的会被自动调用QT使用信号机制的步骤如下: 1. 定义信号:在类声明一个信号函数,使用signals关键字,可以有参数也可以没有参数。 2. 定义:在类声明一个函数,使用slots关键字,可以有参数也可以没有参数。 3. 连接信号:使用connect函数将信号进行连接。connect函数有多种重载形式,其最常用的形式是connect(sender, signal, receiver, slot)。 4. 发射信号:使用emit关键字来发射一个信号。 下面是一个简单的例子: ``` class MyObject : public QObject { Q_OBJECT public: MyObject(QObject *parent = nullptr) : QObject(parent) {} signals: void mySignal(int value); public slots: void mySlot(int value) { qDebug() << "Received value: " << value; } }; MyObject obj1, obj2; QObject::connect(&obj1, &MyObject::mySignal, &obj2, &MyObject::mySlot); emit obj1.mySignal(42); ``` 在上面的例子,我们定义了一个名为MyObject的类,它有一个信号mySignal和一个mySlot。我们使用connect函数将obj1的mySignal信号连接到obj2的mySlot上,然后使用emit关键字来发射一个mySignal信号,并传递一个整数参数。当mySignal信号被发射时,mySlot会被自动调用,输出接收到的整数值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Super.Bear

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

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

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

打赏作者

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

抵扣说明:

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

余额充值