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 个槽函数 fun1
,fun2
都会被执行,并且:
-
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具