1、槽函数

作用

   用于组件和组件之间,组件和窗口之间进行通信

  信号:本质上是个函数(很特殊,只有声明,没有定义,没有源码), 所有的组件QT都已经定义好了对应的信号

     如何查看某个类(组件)有哪些信号

                              方法一:右键点击组件,转到槽即可看到该组件的所有信号

                              方法二:打开QT助手,搜索类,查看到所有的信号声明和介绍

    槽函数 : 就是个普通函数,当信号产生的时候,槽函数会被自动调用执行相应的任务

生成槽函数

  右键转到槽函数

  QT工程自动生成了如下代码:

//声明一个私有的槽函数   slots槽函数的声明
private slots:   
    void on_pushButton_clicked();
void MainWindow::on_pushButton_clicked()  //槽函数的定义
 {

 }

关联信号与槽函数

  方法一:借助集成开发环境QT creator自动关联--》右键转到槽函数

  方法二:程序员自己写代码关联信号与槽函数

connect(sender, &Sender::signal, receiver, &Receiver::slot);
connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
参数:  sender --》信号的发送者
       signal --》发送什么信号
       receiver --》信号的接收者
       method --》要调用执行的槽函数
比如:connect(ui->bt2,SIGNAL(clicked()),this,SLOT(fun()));
// 目前官方推荐的写法优点在实际编程中不用考虑参数类型,注意:当前类名为对应函数的类
connect(ui->bt2,&当前类名::clicked,this,&当前类名::fun);

信号与槽的特点

  • 一个类如果要使用信号与槽,必须在这个类的定义中加上Q_OBJECT这个宏定义
  • 同一个信号可以关联多个不同的槽函数(此时多个槽函数都会被调用,槽函数的调用顺序是依照你关联的先后顺序依次调用)
  • 同一个槽函数可以被不同的信号关联
  • 信号如果带参数,槽函数可以带参数,也可以不带
    void cursorPositionChanged(int oldPos, int newPos)  //这个信号带有两个int类型的参数
    void editingFinished()  //这个信号不带任何参数

     

关联槽信号,获取信号对象

复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //多个不同对象关联一个槽函数,点击不同的按钮执行的是同一个函数
    connect(ui->bt1,SIGNAL(clicked()),this,SLOT(fun()));
    connect(ui->bt2,SIGNAL(clicked()),this,SLOT(fun()));                                           // 改变参数类型,必须在这里进行更改
    connect(ui->bt3,&QAbstractButton::clicked,this,&MainWindow::fun);            // 可以随时变换参数 不管信号连接
    connect(ui->le,SIGNAL(editingFinished()),this,SLOT(fun()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::fun()
{
     //由于四个不同的对象,共用一个槽函数fun()
    //判断究竟是谁发送信号调用了fun
    QObject *obj = sender(); //获取信号的发送者   // 第一种 不用进行类型转化  这些控件基本都是QObject的后代
    qDebug()<<"obj = "<<obj->objectName();

    QPushButton *button = qobject_cast<QPushButton *>(sender());    //第二种 一般比较喜欢这种 专一性
    qDebug()<<"button = "<<button->objectName();   //注意editingFinished点击时会错误,因为它不属于QPushButton类  所以为他另作一个槽函数或者使用第一种

    if (button == ui->bt1)
    {
        qDebug()<<"我肯定地告诉你,你点击的是按钮1";
    }
    if (button==ui->bt2)
    {
        qDebug()<<"我肯定地告诉你,你点击的是按钮2";
    }
}
复制代码

 

信号和槽函数带参数

复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //手动关联QLineEdit的cursorPositionChanged(int oldPos, int newPos) 信号
    connect(ui->lineEdit,SIGNAL(cursorPositionChanged(int,int)),this,SLOT(fun(int,int)));
}

MainWindow::~MainWindow()
{
    delete ui;
}


//void MainWindow::fun()  不带参数,表示fun拒绝接收信号传递过来的参数
void MainWindow::fun(int arg1,int arg2)
{
    qDebug()<<"自己写的槽函数:  "<<arg1<<"    "<<arg2;
}
复制代码

 

 Lambda表达式

槽函数还可以直接写成 lambda 表达式的形式。

C++11 引入了 lambda 表达式,用于定义并创建匿名的函数对象,可以使代码更加的简洁

Qt 是基于 C++ 的一个 GUI 框架,它完全支持 C++ 的语法,因此在 Qt 中也是可以使用 lambda 表达式的。

学习 Qt 多多少少都需要了解一些 C++ 的语法

不过如果你对 C++ 中的 lambda 表达式不了解,也不用担心,这里我们先复习一下 C++ 中 lambda 表达式的使用

lamda 表达式在现代语言中,非常的普遍。lamda 表达式简化了我们的一些语法,使得代码的表达更加的清晰,便于书写与阅读。

一些现代语言,比如 java、python、kotlin 等语言都是支持 lambda 表达式的

C++ 中的 lambda 表达式,其实就是匿名函数,语法如下:

[capture](parameters) option -> return-type { body }
  • capture:捕获列表,可选

捕捉列表总是出现在 lambda 表达式的开始。实际上,[]是 lambda 引出符,编译器根据该引出符判断接下来的代码是否是 lambda 表达式。

捕捉列表能够捕获上下文中的变量,以在 lambda 表达式内使用,主要有如下几种情况:

复制代码
# 不捕获任何变量
[] 

# 按引用捕获外部作用域中所有变量,在 lambda 表达式内使用
[&]

# 按值捕获外部作用域中所有变量,在 lambda 表达式内使用
# 按值捕获的变量,在 lambda 表达式内是只读的,不能修改赋值
[=]

# 捕获外部作用域中所有变量,其中foo变量按引用捕获,其他的按值捕获
[=, &foo] 

# 按值捕获bar变量,同时不捕获其他变量
[bar] 

# 捕获当前类中的 this 指针
# 捕获了 this 就可以在 lambda 中使用当前类的成员变量和成员函数。
# 如果已经使用了 & 或者 =,就默认添加此选项。
[this]
复制代码
  • parameters:参数列表,可选

  • option:函数选项,可选

  • return-type:返回值类型,可选。没有返回值的时候也可以连同符号->一起省略

  • body:函数体

    复制代码
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        ...
            
        // 演示lambda表达式
    
        // 3.1 匿名函数的定义
    #if 0
        []() {
            qDebug() << "lambda...";
        };
    #endif
    
        // 3.2 匿名函数的调用
    #if 0
        []() {
            qDebug() << "lambda...";
        }();
    #endif
    
        int a =10;
    
        // 3.3 不捕获任何变量
        // Variable 'a' cannot be implicitly captured in a lambda with no capture-default specified
    #if 0
        []() {
            qDebug() << a;
        }();
    #endif
    
        // 3.4 按引用捕获
    #if 0
        [&]() {
            qDebug() << a++;    // 10
        }();
        qDebug() << a;          // 11
    #endif
    
        // 3.5 按值捕获
        // 按值捕获的变量,在 lambda 表达式内是只读的,不能修改赋值
    #if 0
        [=]() {
            // Cannot assign to a variable captured by copy in a non-mutable lambda
            qDebug() << a++;
        }();
    #endif
    
        // 3.6 按值捕获 + mutalbe 选项
        // 添加 mutable 选项,就可以在 lambda 内修改捕获的变量了
        // 并且=这种方式,是按值传递的,里面的修改,不会影响外边。
    #if 0
        [=]() mutable {
            qDebug() << a++;    // 10
        }();
        qDebug() << a;          // 10
    #endif
    
        // 3.7 参数
        // 添加 mutable 选项,就可以在 lambda 内修改捕获的变量了
        // 并且=这种方式,是按值传递的,里面的修改,不会影响外边。
    #if 0
        [](int x, int y) {
            qDebug() << x + y;    // 3
        }(1, 2);
    #endif
    
        // 3.8 返回值
        // 返回值可以省略,编译器会自动推断 lambda 表达式的返回值类型
        // 返回值省略时,也可以连同符号`->`一起省略
    #if 0
        int sum = [](int x, int y) -> int {
            return x + y;
        }(1, 2);
        qDebug() << sum;    // 3
    #endif
    
    #if 1
        int sum = [](int x, int y) {
            return x + y;
        }(1, 2);
        qDebug() << sum;    // 3
    #endif
    }槽函数使用 lambda 表达式
    复制代码

 槽函数使用Lambda表达式

复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 1.使用 SIGNAL/SLOT 的方式连接信号和槽
    connect(ui->btnMax, SIGNAL(clicked()), this, SLOT(showMaximized()));

    // 2.使用函数地址的方式连接信号和槽
    connect(ui->btnNormal, &QPushButton::clicked, this, &::QMainWindow::showNormal);
        
    // 3.UI设计师界面-转到槽
    // 直接在UI界面,鼠标点击完成信号槽的连接
        
    // 4.UI设计师界面-信号槽编辑器
    // 直接在UI界面,鼠标点击完成信号槽的连接

    // 5. 使用lambda 表达式做槽函数
    connect(ui->btnSetWindowTitle, &QPushButton::clicked, this, [this]() {
        this->setWindowTitle("[连接信号槽的 5 种方式]");
    });
}
复制代码

 

 信号槽扩展

 连接重载信号槽

在信号和槽存在重载时,Qt4 和 Qt5 的写法是有区别的:

  • Qt4 方式

    可以在 SIGNAL/SLOT 中指定函数参数类型,因此写法比较简单;

  • Qt5 方式

    指定信号和槽时,只能指定函数名,无法向 Qt4 那样指定函数参数类型,需要单独定义函数指针,写法上稍显麻烦。

 

复制代码
    // 1、Qt4信号槽的连接:SIGNAL/SLOT
#if 0
    Commander commander;
    Soldier soldier;

    connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));
    connect(&commander, SIGNAL(go(QString)), &soldier, SLOT(fight(QString)));

    emit commander.go();
    emit commander.go("freedom");
#endif

    // 2、Qt5信号槽的连接:函数地址
#if 0
    Commander commander;
    Soldier soldier;

    // 没有同名的信号和槽时,可以直接这样写。因为不存在二义性
    // connect(&commander, &Commander::go, &soldier, &Soldier::fight);

    // 有同名的信号和槽时,需要向下面这样定义函数指针。因为存在二义性
    // 编译器自动推断:将无参的信号go和无参的槽,赋值给函数指针(ctrl+鼠标点击可以智能跳转过去)
    void (Commander::*pGo)() = &Commander::go;
    void (Soldier::*pFight)() = &Soldier::fight;
    connect(&commander, pGo, &soldier, pFight);

    // 编译器自动推断:将有参的信号go和有参的槽,赋值给函数指针(ctrl+鼠标点击可以智能跳转过去)
    void (Commander::*pGoForFreedom)(QString) = &Commander::go;
    void (Soldier::*pFightForFreedom)(QString) = &Soldier::fight;
    connect(&commander, pGoForFreedom, &soldier, pFightForFreedom);

    emit commander.go();
    emit commander.go("freedom");
#endif
复制代码

一个信号连接多个槽

connect(sender, SIGNAL(signal), receiver1, SLOT(fun1()));
connect(sender, SIGNAL(signal), receiver2, SLOT(fun2()));

这样,当 signal 这个信号发出时,它连接的 2 个槽函数 fun1fun2 都会被执行,并且:

  • Qt4

    信号发射时,与之相连接的槽函数的执行顺序是随机的。

  • Qt5+

    信号发射时,这些槽函数的执行顺序与建立连接的顺序相同。

// 士兵1很勇敢,收到冲锋的信号后,开始战斗
connect(&commander, SIGNAL(go()), &soldier1, SLOT(fight()));

// 士兵2很怕死,收到冲锋的信号后,开始逃跑
connect(&commander, SIGNAL(go()), &soldier2, SLOT(escape()));

首先,在 Soldier 类中添加槽函数的声明和定义,如下:

复制代码
// Soldier.h
class Soldier : public QObject
{
    ...

public slots:
    void fight();
    void fight(QString);

    // 添加一个“逃跑”的槽函数
    void escape();
};

// Soldier.cpp
void Soldier::escape()
{
    qDebug() << "i'm afraid of death, escape...";
}
复制代码

然后,连接信号槽并发送信号,如下:

复制代码
    // 3、一个信号连接多个槽函数
#if 1
    Commander commander;
    Soldier soldier1;
    Soldier soldier2;
    
    // 士兵1很勇敢,收到冲锋的信号后,开始战斗
    connect(&commander, SIGNAL(go()), &soldier1, SLOT(fight()));

    // 士兵2很怕死,收到冲锋的信号后,开始逃跑
    connect(&commander, SIGNAL(go()), &soldier2, SLOT(escape()));

    emit commander.go();
#endif
复制代码

多个信号连接一个槽

可以将多个信号连接到同一个槽函数,如下:

connect(sender, SIGNAL(signal1), receiver, SLOT(fun()));
connect(sender, SIGNAL(signal2), receiver, SLOT(fun()));

这样,当 signal1 和 singnal2 这 2 个信号发出时,都会执行槽函数 fun

接下来,以《6 自定义信号槽》中 “长官和士兵” 的例子为例:

// 当 commander 发射 go 信号和 move 信号时,都会执行士兵的 fight 槽函数,开始战斗
connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));
connect(&commander, SIGNAL(move()), &soldier, SLOT(fight()));

接下来一步步实现这个需求:

首先,在 Commander 类中新添加一个 move 的信号,如下:

复制代码
class Commander : public QObject
{
    ...
        
signals:
    void go();
    void go(QString);

    // 新添加一个 move 信号
    void move();
};
复制代码

然后,连接信号槽并发送信号,如下:

复制代码
    // 4、多个信号连接一个槽函数
#if 1
    Commander commander;
    Soldier soldier;

    // 当 commander 发射 go 信号和 move 信号时,都会执行士兵的 fight 槽函数,开始战斗
    connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));
    connect(&commander, SIGNAL(move()), &soldier, SLOT(fight()));

    emit commander.go();
    emit commander.move();
#endif
复制代码

信号连接信号

信号不仅可以连接槽, 还可以和连接信号,如下:

connect(obj1, SIGNAL(signal1), obj2, SIGNAL(signal2));

这样,当 obj1 发送 signal1 信号时,就会触发 obj2 发送 signal2 信号。

// 按钮的点击会发射clicked信号 => commander发射move信号 => soldier执行escapse槽函数
connect(ui->btnAction, &QPushButton::clicked, commander, &Commander::move);
connect(commander, &Commander::move, soldier, &Soldier::escape);
class MainWindow : public QMainWindow
{
    ...

    // 在 MainWindow 中,添加 commander 和 soldier 两个指针类型的成员变量
    Commander *commander;
    Soldier *soldier;
};
复制代码
    // 5、信号连接信号
#if 1
    // 首先,成员变量初始化
    commander = new Commander();
    soldier = new Soldier();

    // 然后,信号连接信号 + 信号连接槽
    connect(ui->btnAction, &QPushButton::clicked, commander, &Commander::move);
    connect(commander, &Commander::move, soldier, &Soldier::escape);
#endif
复制代码

此时,点击按钮,按钮会发射 clicked 信号, 接着 commander 发射 move 信号,move 信号的发射,会去执行 soldier 的 escape 槽函数

注意:

此时的 commander 和 soldier 要定义为类的成员变量。

因为如果把 commander 和 soldier 定义为局部变量,MainWindow 构造执行完毕后,这两个变量就已经释放了

断开连接 - disconnect

disconnect 用于断开信号和槽之间已经建立的连接。

这种情况并不常用,因为当一个对象 delete 之后, Qt 自动取消所有连接到这个对象上面的槽。

复制代码
    // 6、断开信号的连接
#if 1
    Commander commander;
    Soldier soldier;

    connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));
    connect(&commander, SIGNAL(go(QString)), &soldier, SLOT(fight(QString)));

    emit commander.go();
    
    // 断开所有连接到 commander 信号上的槽函数
    commander.disconnect();
    
    emit commander.go("freedom");  // 对应的槽函数不会执行
#endif
复制代码

 

————————————————
版权声明:本文为CSDN博主「大轮明王讲QT」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bili_mingwang/article/details/126202191

 

 

 

 

  

posted @   秃头的C#  阅读(643)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示