【Qt】信号和槽(四)信号和槽断开连接,lambda表达式定义槽函数,小结

张开发
2026/4/5 15:52:33 15 分钟阅读

分享文章

【Qt】信号和槽(四)信号和槽断开连接,lambda表达式定义槽函数,小结
小编个人主页详情—请点击小编个人gitee代码仓库—请点击Qt系列专栏—请点击倘若命中无此运孤身亦可登昆仑送给屏幕面前的读者朋友们和小编自己!目录前言一、信号和槽断开连接二、lambda表达式定义槽函数三、小结总结前言【Qt】信号和槽三带参数的信号函数和槽函数信号和槽存在的意义——书接上文 详情请点击——本文由小编为大家介绍——【Qt】信号和槽四信号和槽断开连接lambda表达式定义槽函数小结一、信号和槽断开连接我们可以使用disconnect来断开信号和槽的连接而disconnect的使用方式和connect是非常类似的下面我们就来学习一下首先我们建立一个项目Signal_6那么我们先拖拽左侧的Push Button拖拽两个第一个按钮修改文本为修改窗口标题第二个按钮修改文本为使用这个按钮切换槽函数然后我们右击第二个按钮点击转到槽选择clicked让Qt帮我们生成槽函数clicked的声明和定义#ifndefWIDGET_H#defineWIDGET_H#includeQWidgetQT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACEclassWidget:publicQWidget{Q_OBJECTpublic:Widget(QWidget*parentnullptr);~Widget();publicslots:voidhandleClick();voidhandleClick2();privateslots:voidon_pushButton_2_clicked();private:Ui::Widget*ui;};#endif// WIDGET_H所以Widget.h中我们就可以声明按钮1对应的槽函数handleClick槽函数handleClick2#includewidget.h#includeui_widget.h#includeQDebugWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);connect(ui-pushButton,QPushButton::clicked,this,Widget::handleClick);}Widget::~Widget(){deleteui;}voidWidget::handleClick(){this-setWindowTitle(标题1);qDebug()handleClick;}voidWidget::handleClick2(){this-setWindowTitle(标题2);qDebug()handleClick2;}voidWidget::on_pushButton_2_clicked(){disconnect(ui-pushButton,QPushButton::clicked,this,Widget::handleClick);connect(ui-pushButton,QPushButton::clicked,this,Widget::handleClick2);}接下来我们在槽函数handleClick中修改窗口标题为标题1然后使用qDebug()打印日志即可那么我们初始的时候想要按钮1修改窗口标题连接handleClick这个槽函数所以此时点击按钮1修改窗口标题就会将窗口的标题修改为标题1打印日志那么别忘了小编讲解的是信号和槽断开连接所以如何断开呢那么就是使用disconnect那么断开哪个信号函数和槽函数的连接呢我们断开按钮1修改窗口标题发出的Qt内置的信号和自定义槽函数handleClick的连接所以如何断开呢我们点击按钮2使用这个按钮切换槽函数进行断开按钮1对应的信号和槽的连接所以我们在按钮2对应的槽函数on_pushButton_2_clicked中使用disconnect断开连接那么使用disconnect的方式和使用connect的方式相同所以这里小编就不过多赘述了所以此时按钮1对应的信号和槽的连接就已经被断开了此时我们再在on_pushButton_2_clicked中使用connect将按钮1对应的信号和自定义槽函数handleClick2进行连接那么对于自定义槽函数handleClick2我们如何实现呢那么同样的道理修改窗口标题为标题2然后使用qDebug()打印日志即可所以此时再点击按钮1窗口标题会修改为标题2然后打印日志运行结果如下所以如上当程序启动此时按钮1绑定的是修改标题为标题1的槽函数所以我们点击按钮1的时候此时修改了窗口标题为标题1并且打印了日志当我们按下按钮2的时候此时按钮1对应的点击信号所绑定的自定义槽函数就已经被断开了然后按钮1对应的点击信号就被重新绑定为修改窗口标题为标题2的槽函数所以此时我们再点击按钮1此时窗口标题就被修改为了标题2并且打印日志所以disconnect可以断开信号和槽的连接并且disconnect的使用是和connect是十分类似的但是在实际开发中disconnect的使用比较少大部分情况下就是把信号和槽连接好之后就不关心了而这里使用disconnect的场景是主动断开当前信号所连接的槽函数然后信号再重新连接绑定到另一个槽函数上#includewidget.h#includeui_widget.h#includeQDebugWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);connect(ui-pushButton,QPushButton::clicked,this,Widget::handleClick);}Widget::~Widget(){deleteui;}voidWidget::handleClick(){this-setWindowTitle(标题1);qDebug()handleClick;}voidWidget::handleClick2(){this-setWindowTitle(标题2);qDebug()handleClick2;}voidWidget::on_pushButton_2_clicked(){// disconnect(ui-pushButton, QPushButton::clicked, this, Widget::handleClick);connect(ui-pushButton,QPushButton::clicked,this,Widget::handleClick2);}小编那么如果这里我们在按钮2对应的槽函数中不断开按钮1对应的信号和槽函数而是继续将按钮1对应的信号再和修改窗口标题为标题2的槽函数进行连接所以此时理论上来讲按钮1对应的信号就连接了两个槽函数别忘了我们在修改标题的两个自定义槽函数中都设置了日志的打印所以此时点击按钮2之后按钮1对应的信号会再原有的槽函数的基础上在连接一个槽函数此时再点击按钮1就会有两个日志的打印所以如上小编将按钮2对应的槽函数中disconnect的代码注释掉下面小编来验证一下运行结果如下如上点击按钮2之后再点击按钮1此时会出现两个日志的打印而两个日志的打印分别在两个用于修改窗口标题的自定义槽函数中所以此时两个日志的打印表示用于修改窗口标题的两个自定义槽函数都执行了即按钮1对应的信号此时就成功的绑定连接了两个自定义槽函数所以一个信号是可以连接多个槽函数的二、lambda表达式定义槽函数其实定义槽函数的时候也可以使用C语法中的lambda表达式的关于lambda表达式的讲解详情请点击——关于lambda表达式其实很多编程语言都进行了支持这是提供的语法糖lambda表达式本质就是一个匿名函数主要应用在回调函数中进行使用通常是一次性使用当然也可以使用auto识别函数类型延长生命周期进行调用所以此时我们创建一个项目名称为Signal_7基类为QWidget派生类为Widget的项目#includewidget.h#includeui_widget.h#includeQDebug#includeQPushButtonWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);QPushButton*buttonnewQPushButton(this);button-setText(按钮);button-move(200,200);connect(button,QPushButton::clicked,this,[](){qDebug()lambda被执行了;button-move();});}Widget::~Widget(){deleteui;}所以呢我们要实现一个什么功能的GUI程序呢我们要实现一个点击按钮移动窗口/按钮的GUI程序所以此时我们在Widget的构造函数中创建一个按钮button将button中的文本设置为按钮然后移动按钮到200200的位置接下来由于我们是使用lambda表达式定义槽函数所以我们直接connect连接按钮对应的信号和槽函数即可对于这个槽函数我们使用lambda表达式所以lambda表达式的定义就是{}那么进入lambda表达式首先我们使用qDebug()打印日志即可如上代码那么接下来我们就想要使用按钮button调用move移动按钮的位置了可是此时在lambda表达式中却无法使用button这是因为lambda表达式是一个回调函数这个回调函数自己的一个作用域这个回调函数无法获取上层main函数作用域中的变量#includewidget.h#includeui_widget.h#includeQDebug#includeQPushButtonWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);QPushButton*buttonnewQPushButton(this);button-setText(按钮);button-move(200,200);connect(button,QPushButton::clicked,this,[button](){qDebug()lambda被执行了;button-move(300,300);});}Widget::~Widget(){deleteui;}而lambda为了解决这个问题引入了变量捕获的语法通过变量捕获就可以获取获取到外层作用域中的变量了而直接填写变量名是传值捕获的方式运行结果如下无误所以此时运行程序那么我们点击按钮果然按钮的位置向右下方移动并且应用程序窗口上也输出了日志lambda被执行既然按钮可以被移动那么程序窗口也可以被移动那么也就是对应的使用this指针调用move移动窗口#includewidget.h#includeui_widget.h#includeQDebug#includeQPushButtonWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);QPushButton*buttonnewQPushButton(this);button-setText(按钮);button-move(200,200);connect(button,QPushButton::clicked,this,[button,this](){qDebug()lambda被执行了;button-move(300,300);this-move(100,100);});}Widget::~Widget(){deleteui;}所以此时我们传值捕获this指针然后使用this指针调用move移动到100100就可以达到移动程序窗口的目的运行结果如下无误此时如上图程序运行起来那么我们点击按钮按钮相对于父窗口向右下角移动了同样的程序窗口也向左上角100100的位置移动了并且还打印了日志那么此时lambda使用传值捕捉的方式捕捉了两个变量button和this#includewidget.h#includeui_widget.h#includeQDebug#includeQPushButtonWidget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui-setupUi(this);QPushButton*buttonnewQPushButton(this);button-setText(按钮);button-move(200,200);connect(button,QPushButton::clicked,this,[](){qDebug()lambda被执行了;button-move(300,300);this-move(100,100);});}Widget::~Widget(){deleteui;}可是如果当前外层的变量有10个20个甚至更多我都想捕捉都想使用那么这样一个一个的写变量名进行传值捕捉效率太低即如果当前lambda想使用外层更多的变量怎么办所以此时我们可以使用[]的方式就可以将上层作用域中所有的变量名全部以传值捕捉的方式捕捉到lambda表达式中进行使用运行结果如下无误后面如果我们要实现的槽函数比较简单而且是一次性使用那么我们就会经常写作这种lambda表达式的形式另外我们也要确保捕捉到lambda表达式内部的变量是有意义的当用户点击按钮的时候lambda对应的回调函数才会被执行但是由于用户什么时候点击按钮所以对于回调函数什么时候执行我们是不清楚的我们要确保的就是无论用户何时点击了按钮捕捉到的变量都能正常使用所以此时我们就来研究一下我们的代码中使用传值捕捉的button和this指针是否符合要求那么首先对于这里的button是在堆上new出来的变量生命周期跟随整个窗口也就是将button对应的堆上的变量挂接到对象树上当窗口关闭的时候才会释放所以我们对button进行值拷贝的传值捕获对应的值就指向堆上变量那么只要窗口没有关闭那么我们随时可以进行访问那么对于this指针来讲this指针指向的是Widget的对象w而虽然Widget的对象w是在栈上定义的属于局部变量但是由于Widget的对象w是属于main函数中的局域变量所以也就意味着main函数销毁Widget的对象w才释放而main函数结束了也就意味着进程结束了所以换句话来说只要进程不结束窗口就没有被关闭/销毁Widget的对象w就可用进而this指针就可用所以综上对于我们在lambda表达式中使用传值捕捉的button和this指针都符合只要进程不结束窗口不关闭/销毁那么就可以使用所以在这个过程中无论用户什么时候点击按钮进而执行槽函数对应的lambda表达式的语句button和this指针都有效并且我们还要知道lambda除了可以按照传值捕获[]的方式捕获变量还可以按照传引用捕获[]的方式捕获变量但是在Qt中很少按照传引用的方式捕获变量因为我们在Qt中捕获的变量一般是各种控件的指针而指针变量按照传值或者传引用的方式进行传递都可以可是传引用的话还有一个致命的问题那么是什么呢就是如果lambda表达式按照传引用的方式捕捉的变量更多的还要关注这个引用的变量本身的生命周期例如上图的指针变量button它是一个局部是指针变量它是出于Widget的构造函数中此时采用引用的方式捕捉了button那么此时Widget的构造函数执行结束构造函数的栈帧释放进而这个局部的指针变量的生命周期随构造函数的范围此时构造函数结束这个局部的指针变量的生命周期也就结束了所以有可能这个button指针被置为了nullptr更关键的来了如果此时lambda中采用的是传引用的方式捕捉的button指针那么用户如果按下按钮那么此时lambda中访问了button指针别忘了此时button指针此时由于是传引用捕捉的并且由于构造函数的结束button指针已经被置为了nullptr所以此时进行访问就会形成nullptr空指针的访问进而就有可能造成程序的崩溃所以如上采用传引用的方式捕捉的风险很大而采用传值引用的方式进行捕捉就是对指针的拷贝这样即使button在外面被置为了nullptr我lambda也不需要关注因为我lambda内部已经形成了对button指针指向的堆上的变量的地址的拷贝所以只要lambda存在那么这个拷贝就有效所以我们在实际开发的时候如果使用lambda表达式尽量采用传值捕捉的方式捕捉上层作用域的变量进行使用那么对于lambda表达式是C11中引入的对于Qt 5以及更高版本中默认就是按照C11来编译的如上那么在.pro文件中已经有了CONFIG c11但是如果是在Qt 4或者更老的版本中就需要手动在.pro文件中加上C11的编译选项CONFIG c11三、小结那么小编在前面以及上文中讲解了Qt的信号和槽的核心机制如下那么接下来小编就来总结一下如下内容【Qt】信号和槽一基本概念connect函数详情请点击——【Qt】信号和槽二自定义信号函数和槽函数详情请点击——【Qt】信号和槽三带参数的信号函数和槽函数信号和槽存在的意义详情请点击——首先小编讲了信号槽是什么并且将其和Linux中的信号进行了对比那么信号槽分为三个要素1信号源2信号的类型3信号的处理方式也就是通过回调函数的方式进行处理那么接下来小编信号槽的连接connect接下来就是查阅文档的方法在查阅的信号槽时候如果当前类没有那么很有可能要去这个类的父类/爷爷类/祖宗类中去查询1一个控件内置了哪些信号信号都是何时触发的2一个控件内置了哪些槽槽都是什么作用接下来小编讲解了自定义信号信号本质都是成员函数我们只需要写信号函数的声明对于信号函数的定义是由Qt自动生成的无需我们关心1使用Qt自定义的关键字signals: 去表征当前成员函数是信号函数当qmake构建项目的时候会调用扫描工具去扫描signals: 下的信号函数并且生成信号函数的定义2自定义信号需要我们手动使用代码emit 信号函数函数名()的方式进行发射当然emit也可以不写信号和槽还可以带有参数发射信号的时候信号函数会把参数传递给对应的槽信号的参数类型和顺序要和槽的参数一致信号的参数个数要大于等于槽的参数个数信号槽存在的意义1解耦合将触发用户操作的控件和处理对应用户操作的逻辑的解耦合2实现类似于MySQL的多对多的效果一个信号可以connect连接多个槽一个槽可以被多个信号连接disconnect可以实现信号和槽连接的断开便于将信号重新绑定到另外一个槽上lambda表达式可以简化槽函数的定义那么在信号槽存在的意义中小编提到了一个名词叫做解耦合其中的耦合我们该如何进行理解呢同样的这个耦合是计算机中常见的术语并且与耦合对应的还有内聚我们又该如何理解内聚呢并且我们在软件开发的过程中追求的是高内聚低耦合又该如何进行理解呢如下首先我们先来看耦合所谓的耦合就是指当前存在两个功能模块的实现1当前功能1和功能2关联很大那么如果功能1出现了问题无法运行因此受到功能1的影响功能2也出现问题并且无法运行即功能1出现问题对功能2有非常大的影响此时我们就称功能1和功能2的关系是高耦合的2同样的道理当前功能1和功能2的关联不大此时如果功能1出现了问题无法运行但是此时功能2和功能1关联不大所以功能2可以照常运行不受功能1的干扰即功能1出现问题对功能2的影响不大此时我们就称功能1和功能2之间的关系是低耦合的所以此时我们可以看到那么在一个软件开发的过程中我们是期望两个功能之间互相不影响各自的运行的这就好比要上线一个程序如果里面的各个功能都是高耦合的那么一旦一个功能模块出现了问题那么整个程序都无法运行了这也不是我们期望看到的所以我们期望的是在一个程序中即使一个功能模块出现了问题其它的功能模块还可以正常运行不至于造成整个程序都无法运行了所以我们期望的是软件开发工程中各个功能模块尽量是低耦合的关系接下来我们来看内聚那么存在一个功能模块1写代码的时候如果实现一个功能模块的时候围绕这个功能的相关代码被放到整个项目的各个地方如果整个项目的代码有好几百万行那么对于这个功能的开发维护的成本变得非常高此时我们就称这个功能是低内聚的2那么在写代码的时候如果实现一个功能模块的时候围绕这个功能的相关代码都被集中放在一起了此时即使整个项目的代码有好几百万行那么对于这个功能的开发维护的成本也比较低此时我们就称这个功能是高内聚的所以我们在软件开发的过程中对于一个功能模块的实现期望是高内聚的只有这样可维护性才会变高所以综上此时我们就理解了耦合内聚并且对于一个功能和另外一个功能之间我们追求的是低耦合对于一个功能内部我们追求的是高内聚所以在软件开发的范畴我们写代码追求的是高内聚低耦合总结以上就是今天的博客内容啦,希望对读者朋友们有帮助水滴石穿坚持就是胜利读者朋友们可以点个关注点赞收藏加关注找到小编不迷路

更多文章