当前位置: 首页 > news >正文

Qt基础:信号槽

信号槽

  • 1. 概述
    • 1.1 信号
    • 1.2 槽
    • 1.3 信号与槽的连接
  • 2. 标准信号槽
    • 2.1 使用
  • 3. 自定义信号槽
    • 3.1 自定义信号
    • 3.2 自定义槽
    • 3.3 信号槽特点
    • 3.4 信号槽连接方式
  • 4. Lambda表达式
    • 4.1 语法规则
    • 4.2 定义和调用

1. 概述

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

1.1 信号

信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候Qt对应的窗口类会发出某个信号,以此对用户的挑选做出反应。
信号的本质就是事件,比如:按钮单击、双击;窗口刷新;鼠标移动、鼠标按下、鼠标释放;键盘输入
我们对哪个窗口进行操作, 哪个窗口就可以捕捉到这些被触发的事件,Qt框架给我们发出某个特定信号。信号的呈现形式就是函数,也就是说某个事件产生了, Qt框架就会调用某个对应的信号函数, 通知使用者。

在QT中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测。

1.2 槽

槽函数是一类特殊的功能的函数,在编码过程中也可以作为类的普通成员函数来使用。之所以称之为槽函数是因为它们还有一个职责就是对Qt框架中产生的信号进行处理。
在Qt中槽函数的所有者也是某个类的实例对象。

1.3 信号与槽的连接

在Qt中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起。在Qt中我们需要使用QOjbect类中的connect函数进二者的关联。

连接信号和槽的connect()函数原型如下, 其中PointerToMemberFunction是一个指向函数地址的指针

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() 如下:
connect(const QObject *sender, &QObject::signal, const QObject *receiver, &QObject::method);
  • connect函数相对于做了信号处理动作的注册
  • 调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的method也不会被调用
  • method槽函数本质是一个回调函数, 调用的时机是信号产生之后, 调用是Qt框架来执行的
  • connect中的sender和recever两个指针必须被实例化了, 否则conenct不会成功

2. 标准信号槽

在Qt提供的很多标准类中都可以对用户触发的某些特定事件进行检测, 因此当用户做了这些操作之后, 事件被触发类的内部就会产生对应的信号, 这些信号都是Qt类内部自带的, 因此称之为标准信号。同样的,在Qt的很多类内部为我了提供了很多功能函数,并且这些函数也可以作为触发的信号的处理动作,有这类特性的函数在Qt中称之为标准槽函数。
系统自带的信号和槽通常如何查找呢,这个就需要利用帮助文档了,比如在帮助文档中查询按钮的点击信号,那么需要在帮助文档中输入QPushButton。

首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该看当前类从父类继承下来了哪些信号。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1 使用

插入按钮控件
在这里插入图片描述
在mainwindow的构造函数中输入:

#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);connect(ui->closeBtn, &QPushButton::clicked, this, &MainWindow::close);  //建立消息槽
}MainWindow::~MainWindow()
{delete ui;
}

3. 自定义信号槽

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

  1. 要编写新的类并且让其继承Qt的某些标准类
  2. 这个新的子类必须从QObject类或者是QObject子类进行派生
  3. 在定义类的头文件中加入 Q_OBJECT 宏

3.1 自定义信号

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

  1. 信号是类的成员函数
  2. 返回值必须是 void 类型
  3. 信号的名字可以根据实际情况进行指定
  4. 参数可以随意指定, 信号也支持重载
  5. 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字
  6. 信号函数只需要声明, 不需要定义(没有函数体实现)
  7. 在程序中发射自定义信号: 发送信号的本质就是调用信号函数。
    • 习惯性在信号函数前加关键字: emit, 但是可以省略不写;
    • emit只是显示的声明一下信号要被发射了, 没有特殊含义。
    • 底层 emit == #define emit
emit m_friend ->hungry();
// 举例: 信号重载
// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
class Test : public QObject
{Q_OBJECT
signals:void testsignal();// 参数的作用是数据传递, 谁调用信号函数谁就指定实参// 实参最终会被传递给槽函数void testsignal(int a);
};

3.2 自定义槽

槽函数就是信号的处理动作,在Qt中槽函数可以作为普通的成员函数来使用。自定义槽函数和自定义的普通函数写法是一样的。
自定义槽的要求和注意事项:

  1. 返回值必须是 void 类型
  2. 槽也是函数, 因此也支持重载
  3. 槽函数需要指定多少个参数, 需要看连接的信号的参数个数
  4. 槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数
    • 槽函数的参数应该和对应的信号的参数个数, 从左到右类型依次对应
    • 信号的参数可以大于等于槽函数的参数个数,即信号传递的数据被忽略了
  5. Qt中槽函数的类型是多样的。可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数)
  6. 槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写)
// 槽函数书写格式举例
// 类中的这三个函数都可以作为槽函数来使用
class Test : public QObject
{public:void testSlot();static void testFunc();public slots:void testSlot(int id);
};

3.3 信号槽特点

  1. 一个信号可以连接多个槽函数, 发送一个信号有多个处理动作
    • 需要写多个connect()连接
    • 槽函数的执行顺序是随机的, 和connect函数的调用顺序没有关系
    • 信号的接收者可以是一个对象, 也可以是多个对象
  2. 一个槽函数可以连接多个信号, 多个不同的信号, 处理动作是相同的
    • 需要写多个connect()连接
  3. 信号可以连接信号。信号接收者可以不处理接收的信号, 而是继续发射新的信号,这相当于传递了数据, 并没有对数据进行处理
connect(const QObject *sender, &QObject::signal, const QObject *receiver, &QObject::siganl-new);
  1. 信号槽是可以断开的
disconnect(const QObject *sender, &QObject::signal, const QObject *receiver, &QObject::method);

3.4 信号槽连接方式

QT5的连接方式:连接的是函数地址

// 语法:
QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);// 信号和槽函数也就是第2,4个参数传递的是地址, 编译器在编译过程中会对数据的正确性进行检测
connect(const QObject *sender, &QObject::signal, const QObject *receiver, &QObject::method);

Qt5中如果信号和槽的函数发生了重载,如果只是用函数名地址来进行连接将发生错误。因为函数重载后,编译将不通过。

//Qt5中函数发生重载时,可以通过定义函数指针的方式指定出函数的具体参数,这样就可以确定函数的具体地址了。void (CGFriend::*friend1)() = &CGFriend::hungry;void (CGFriend::*friend2)(QString) = &CGFriend::hungry;void (Me:: * myslot1)() = &Me::eatMeal;void (Me:: * myslot2)(QString) = &Me::eatMeal;connect(m_friend, friend1, m_me, myslot1);connect(m_friend, friend2, m_me, myslot2);connect(m_friend, friend2, this, &MainWindow::eatSlot);

Qt4的连接是通过宏转换为函数的指针之后进行连接。

//Qt4方式进行连接connect(m_friend, SIGNAL(hungry()), m_me, SLOT(eatMeal()));connect(m_friend, SIGNAL(hungry(QString)), m_me, SLOT(eatMeal(QString)));

注意:

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

4. Lambda表达式

4.1 语法规则

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

[capture](params) opt -> ret {body;};- capture: 捕获列表- params: 参数列表- opt: 函数选项- ret: 返回值类型- body: 函数体
  1. 捕获列表: 捕获一定范围内的变量
    • [] - 不捕捉任何变量
    • [&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
    • [=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
      • 拷贝的副本在匿名函数体内部是只读的
    • [=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
    • [bar] - 按值捕获 bar 变量, 同时不捕获其他变量
    • [&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
    • [this] - 捕获当前类中的this指针
      • 让lambda表达式拥有和当前类成员函数同样的访问权限
      • 如果已经使用了 & 或者 =, 默认添加此选项
  2. 参数列表: 和普通函数的参数列表一样
  3. opt 选项 –> 可以省略
    • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
    • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
  4. 返回值类型:标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略
  5. 函数体:函数的实现,这部分不能省略,但函数体可以为空。

4.2 定义和调用

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

// 匿名函数的定义, 程序执行这个匿名函数是不会被调用的
[](){qDebug() << "hello, 我是一个lambda表达式...";
};// 匿名函数的定义+调用:
int ret = [](int a) -> int
{return a+1;
}(100);  // 100是传递给匿名函数的参数

在Lambda表达式的捕获列表中也就是 []内部添加不同的关键字, 就可以在函数体中使用其外部变量。

// 在匿名函数外部定义变量
int a=100, b=200, c=300;
// 调用匿名函数
[](){// 打印外部变量的值qDebug() << "a:" << a << ", b: " << b << ", c:" << c;  // error, 不能使用任何外部变量
}[&](){qDebug() << "hello, 我是一个lambda表达式...";qDebug() << "使用引用的方式传递数据: ";qDebug() << "a+1:" << a++ << ", b+c= " << b+c;
}();// 值拷贝的方式使用外部数据
[=](int m, int n)mutable{qDebug() << "hello, 我是一个lambda表达式...";qDebug() << "使用拷贝的方式传递数据: ";// 拷贝的外部数据在函数体内部是只读的, 如果不添加 mutable 关键字是不能修改这些只读数据的值的// 添加 mutable 允许修改的数据是拷贝到函数内部的副本, 对外部数据没有影响qDebug() << "a+1:" << a++ << ", b+c= " << b+c;qDebug() << "m+1: " << ++m << ", n: " << n;
}(1, 2);

http://www.mrgr.cn/news/96637.html

相关文章:

  • PHP 开发API接口签名验证
  • npm webpack打包缓存 导致css引用地址未更新
  • 分享一个Drools规则引擎微服务Docker部署
  • mysql JSON_ARRAYAGG联合JSON_OBJECT使用查询整合(数组对象)字段
  • RKNN SDK User Guide学习要点
  • 蓝桥杯15届JAVA_A组
  • 【QT5 网络编程示例】TCP 通信
  • Kong网关研究
  • 【Unity】记录TMPro使用过程踩的一些坑
  • Spark,上传文件
  • HTML中数字和字母不换行显示
  • 数据结构和算法(十一)--图
  • 去中心化稳定币机制解析与产品策略建议
  • ros2--xacro
  • Python-八股总结
  • 【群智能算法改进】一种改进的蜣螂优化算法IDBO[3](立方混沌映射Cubic、融合鱼鹰勘探策略、混合高斯柯西变异)【Matlab代码#92】
  • 【MVC简介-产生原因、演变历史、核心思想、组成部分、使用场景】
  • 【Pandas】pandas Series to_markdown
  • 六种光耦综合对比——《器件手册--光耦》
  • 十五届蓝桥杯省赛Java B组(持续更新..)