当按钮检测到自己被点击,它就发出一个信号 signal ,当对象对这个信号感兴趣,就会使用连接 connect 函数,将要处理的信号与自己的函数 (也称为 ) 绑定到一起处理这个信号

# 信号

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

  • 鼠标点击,双击
  • 窗口刷新 (大小变化)
  • 鼠标移动,鼠标按下,鼠标释放
  • 键盘输入

#

槽函数是一类特殊的功能函数,在编码过程中也可以作为类的普通成员函数来使用。

# connect

在 Qt 中信号和槽都是独立的个体,通过 connect 函数进行二进制关联。

connnect(信号sender发出者,函数指针信号signal函数地址,信号接受者receiver,method方法)
// 当检测到 sender 发送 signal 信号时,receiver 对象调用 method 方法,信号发出之后处理动作
//method 槽函数本质是一个回调函数,调用的时机是信号产生之后,
// 接受者和发出者的必须实例化

# 使用

功能实现:点击窗口上的按钮,关闭窗口
功能分析:

  • 按钮:信号发出者 -> QPushBUtton 类型
  • 窗口:信号的接收者和处理者 -> QWidget 类型
    需要使用的标准信号槽函数
// 单击按钮发出的信号
[signal] void QAbstractButton::clicked(bool checked = false)
// 关闭的窗口的槽函数
[slot] bool QWidget::close();
// 点击按钮关闭窗口
connect(ui->closewindow,&QPushButon::clicked,this,&MainWindow::close)

connect () 操作一般写在窗口的构造函数中,相当于在事件发生之前 qt 框架先进行注册,在程序运行过程中,被点击,即会调用对应的槽函数

# 自定义槽函数

自定义需要的信号和槽

# 满足条件

  • 编写新的类让其继承 qt 的某些标准类
  • 新的子类必须从 QObject类 或者 QObject子类 进行派生
  • 在定义类的头文件中加入 Q_OBJECT宏

# 自定义信号

  • 信号时类的成员函数,需要使用 signals关键字 进行 声明
  • 返回值 须是 void类型
  • 参数可以随意指定,信号也 支持重载
  • 信号函数只需要生命, 不需要定义 (没有函数体实现)
  • 发送信号的本质就是 调用信号函数 ,习惯在信号函数前加关键字 emit 声明信号被发射了
//Qt 类想要使用信号槽机制必须要从 QObject 类派生 (直接派生或间接派生)
Class Test : public QObject
{
	Q_OBJECT
signals:
	void testsignal();
	// 参数的作用是数据传递,调用信号函数需要指定实参
	// 实参最终会被传递给槽函数
	void testsignal(int a);
}

# 自定义槽

槽函数就是信号的处理动作,

# 注意事项

  • 返回值必须是 void类型
  • 槽也是函数,支持 重载
  • 连接的信号有 多少参数 函数也需要有多少个参数 (信号多的函数将会被忽略)
  • Qt 中槽函数的类型可以是类的成员函数,全局函数,静态函数,lambda 表达式 (匿名函数)
  • 槽函数可以使用关键字 slots 声明:public slots,private slots (不能在类外调用),protected slots

女朋友饿了,于是带她去吃饭

// class GirlFriend
class GirlFriend : public QObject
{
    Q_OBJECT
public:
    explicit GirlFriend(QObject *parent = nullptr);
signals:
    void hungry();	            // 不能表达出想要吃什么
    void hungry(QString msg);	// 可以通过参数表达想要吃什么
};
// class Me
class Me : public QObject
{
    Q_OBJECT
public:
    explicit Me(QObject *parent = nullptr);
    //explicit 防止隐式类型转换,对应的函数不能使用隐式类型转换
public slots:
    // 槽函数
    void eatMeal();             // 不能知道信号发出者要吃什么
    void eatMeal(QString msg);  // 可以知道信号发出者要吃什么
};

隐式类型转换

Class Test 
{
public:
	Test(char * str);
	Test(int a);
private:
	char* m_ste;
	int size;
}
// 在调用时可以使用
Test str = "abc"; // 会传递成功
Test str1 = 10;  // 也会传递成功,但是对用户端存在向 str1 不清楚的问题,当 int 前的构造函数加上 explict 就只能使用下面的方法传递参数,此方法将报错。
Test str1(10);

# 信号槽的扩展

一个信号可以连接多个槽函数,发送一个信号有多个处理动作,需要写 多个 connect 连接,槽函数的执行顺序是随机的,

// 建立连接
connnect
// 断开连接
disconnect

# Lambda 表达式

匿名 表达式

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

# 定义与调用

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

# 捕获 列表案例

// 在匿名函数外部定义变量
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);

# 实例

  • UI 界面建立一个 QPushButton ,命名为 close
  • 定义按钮 数字++ 命名为 num
  • 定义 取消按钮数字++ 命名为 close_num
  • 新建 myclass1 类,属于 QObject 建立 信号
signals: // 信号,
    void MySignal(void); // 不需要实现,调用信号使用 emit 信号函数即可
    void Num(int& num); // 信号函数实现数值 +
  • 新建 myclass2类 ,属于 QObject 建立 函数
public slots:
    void MySlot(void); // 槽函数,需要实现
    void NumSlot(int& num); // 信号函数实现数值 +
// 函数实现 
void MyClass2::MySlot()
{
    // 调用 MySignal 槽函数实现
    qDebug()<<"调用了MysSignal槽函数";
}
void MyClass2::NumSlot(int& num)
{
    num ++;
    qDebug()<<"num: "<<num;
}
  • 主函数 实现
// 头文件 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "myclass.h"
#include "myclass2.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void on_num_clicked();
    void on_close_num_clicked();
private:
    Ui::MainWindow *ui;
    MyClass * mc1;
    MyClass2 * mc2;
    int num =0;
};
#endif // MAINWINDOW_H
// 函数实现 
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QLabel>
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    // 锁定全局大小不可放大
    //setFixedSize(400,300);
    // 设置标题
    setWindowTitle("My first No UI");
    mc1 = new MyClass(this);
    mc2 = new MyClass2(this);
    //connect 连接信号与槽
    // 一个信号可以关联多个槽,多个槽可以被多个信号关联
    connect(ui->close,&QPushButton::clicked,mc2,&MyClass2::MySlot); // 调用
    connect(mc1,&MyClass::MySignal,mc2,&MyClass2::MySlot);
    connect(mc1,&MyClass::Num,mc2,&MyClass2::NumSlot);
    connect(ui->num,&QPushButton::clicked,mc1,&MyClass::MySignal);  // 当点击数字信号发出,同时调用另一个信号
    //label 标签
    QLabel *label = new QLabel(this); // 创建 label 是带 this 参数,即显示到当前主页面
    label->setText("你好呀!");
    label->move(200,100);  // 移动位置,以左上角为起点
    emit mc1->MySignal();
    // 或者使用
    // label->setParent(this);
    // 锁定 this 窗口的大小,不能放大
    //button 标签
    QPushButton* button = new QPushButton(this);
    button->setText("点击");
    button->move(200,150);
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::on_num_clicked()
{
    emit mc1->Num(num); // 当点击 num 之后,调用 mc1 函数的 Num 信号,并传递 num 数值
}
void MainWindow::on_close_num_clicked()
{
    // 当点击 close_num 取消数字关联,参数和 connect 内容相同
    disconnect(mc1,&MyClass::Num,mc2,&MyClass2::NumSlot);
}

# 参考资料

  • 爱编程的大丙
  • 嵌入式技术公开课 Qt5~8