信号与槽的介绍
1.信号和槽概述qt中谈到信号涉及到三个要素1.信号源由哪个控件发出信号。2.信号的类型用户进行不同的操作就可能触发不同的信号(点击按钮触发点击信号、在输入框中移到光标触发移到光标的信号等)。3.信号的处理方式槽(slot)其实就是一个函数qqt中使用connect这样的函数把一个信号和一个槽关联起来后续只要信号触发了qt就会自动的执行槽函数。所谓的槽函数本质是上也是一种回调函数。qt中一定是先关联信号和槽然后再触发这个信号顺序不能颠倒否则信号就不知道如何处理了。2. 信号和槽的使用qt中要想使用信号和槽离不开一个关键的函数叫connect它是QObject提供的静态的成员函数。Qt中提供的这些类本身是存在一定的继承关系的了比如QPushButton、QLineEdit等类是继承自QWidget(控件)类的QWidget再往上还有个父类是QObjectQObject就是其它qt内置类的祖宗。由于connect函数是QObject这个类提供的其它qt的类又是间接的继承自这个类所以都可以使用到connect。接下来看看connect的具体的使用方式这是connect的函数原型这个里面有5个参数最后一个是个默认参数(暂时不考虑)。第一个参数要传一个QObject指针的sender其实它就是信号源它就描述了当前信号是哪个控件发出来的。第二个参数描述了信号的种类发出的是哪一个信号。第三个和第四个参数描述的是信号如何处理第三个说的是哪个对象(控件)负责处理第四个说的是这个对象该怎么处理(要处理信号的对象提供的成员函数)。下面写代码来演示一下我们的例子是界面上有个按钮用户点击按钮关闭窗口当前有了窗口和关闭按钮只不过现在点按钮并没有任何效果。继续完善我们的所谓的信号也是qt中的对象内部提供的一些成员函数click是一个slot函数作用是在调用的时候相当于点击了一下按钮。clicked(过去分词)才是要触发的点击信号(图标上有锯代表槽函数带信号的表示信号函数)。connect要求第一个参数和第二个参数是匹配的。button的类型如果是QPushButton*此时第二个参数的信号必须是QPushButton内置的信号(它父类的信号)不能是一个其它的类比如QLineEdit的信号。继续看通过Widget来相应前面的点击信号close是QWidget内置的槽函数Widget继承自QWidget也就继承了父亲的槽函数。close槽函数功能是人家内部实现好的不需要我们去关心具体的功能是关闭当前的窗口/控件。后续针对button进行点击Widget就会关闭。下面测试可以发现点击后就关闭了。写完了代码现在还有两个问题1.怎么知道的QPushButton有个clicked信号怎么知道QWidget有个close槽Qt里到底都提供了哪些内置的信号和槽可以让我们直接使用呢多看文档在QPushButton中没有找到clicked信号。在翻阅文档的时候如果在当前类中没有找到对应的线索不妨看看这个类的父类可以看到QPushButton的父类是QAb子类是QCo。abstract是抽象的意思qt中会提供好几种按钮这些按钮之间存在一些共性内容就把这些共性的东西提取出来放到了QAbstractButton类里面按钮把共性继承再扩展自己的。点过去看看往下翻找看到了信号函数它的子类就都可以使用。点开后也有描述2.connect第二个参数和第四个参数都是char*类型的但我们填写的是函数指针那char*和函数指针是同一个东西吗它们都不是一样的类型。我们点进去看看声明所以指向这个函数的指针是void(*)()类型的指针和char*不是一个类型。那形参是char*我们传的函数指针类型c中不允许用两个不同的指针类型相互赋值这里为什么可以点开文档发现也是char*其实这个函数声明是以前旧版本的Qt的connect函数的声明以前版本中传参的写法和现在其实也是有区别的。给信号参数传参要搭配一个SIGNAL宏给槽函数传参搭配一个SLOT宏搭配宏可以把函数指针分别转为char*。以前版本这样写从Qt5开始对上述写法做了简化不需要写这两个宏了给connect提供了重载版本。重载版本中第二个参数和第四个参数成了泛型参数就允许传入任意类型的函数指针此时connect函数就带有了一定的参数检查功能如果你传入的第一个参数和第二个参数不匹配或者第三个参数和第四个参数不匹配(不匹配说的是2 4参数的函数指针不是1 3参数的成员函数)此时代码编译出错。前面的例子中给的信号是qt内置的槽也是qt内置的但有时候内置的不一定能满足我们的要求所以我们还要能自定义信号和槽。先来说说自定义槽所谓的slot就是一个普通的成员函数自定义一个槽函数操作过程和自定义一个普通的成员函数没有什么区别下面来测试一下再来说说第二种定义槽函数的方式用qt designer先拖动一个Button过去此时就能使用ui-objectName访问自动生成的控件按照第一种方式继续通过connect完成信号槽的连接是这样的但还有更容易的方式选择clicked点击发现qt creator生成了函数的声明和定义我们编写就行了下面测试一下发现第二种方式没有connect在qt中除了通过connect来连接信号槽之外还可以通过函数名字的方式来自动连接。我们看看自动生成的槽函数的名字on是固定的前缀pushButton是按钮的objectName在后面是信号的名字。通过函数名可以了解到把pushButton这个按钮它的clickde信号和当前函数建立连接。当函数名符合上述规则之后qt就能自动的把信号的槽给建立上联系。比如故意把名字后面改为qt没有的信号click试一下此时点击按钮没有任何反应。通过提示也知道qt中调用画红线的这个函数的时候就会触发上述自动连接信号槽的规则这正式在自动生成的ui_widget.h中调用的如果我们通过图形化的界面创建控件还是推荐使用第二种快速的方式来连接信号槽如果我们是通过代码的方式来创建控件还是得收到connect因为这种情况代码中没有调用connectSlotByName函数。下面来说说怎么自定义信号函数qt中也允许自定义信号。自定义函数槽非常关键开发中大部分情况都是需要自定义槽函数的槽函数就是用户触发某个操作之后要进行的业务逻辑。自定义信号比较少见实际开发中很少会需要自定义信号信号就对应到用户的某个操作在GUI中用户能够进行哪些操作是可以穷举的qt内置的信号基本上已经覆盖到了上述所有可能的用户操作。因此使用qt的内置信号就足以应付大部分的开发场景了。咱们的Widget虽然还没有定义任何信号由于继承自QWidget和QObject这两类里面已经提供了一些信号了可以直接使用。下面来看看自定义信号1.所谓的qt的信号本质上也就是一个函数。但是信号则是一类非常特殊的函数程序员只要写出函数声明并且告诉qt这是一个信号即可。这个函数的定义是qt在编译过程中自动生成的(自动生成的过程程序员无法干预)。2.作为信号函数这个函数的返回值必须是void。有没有参数都可以甚至也可以支持重载。下面简单演示一下(signals是qt自己扩展出的关键字qmake时会调用一些代码的分析/生成工具扫描到类中包含signals这个关键字的时候就会自动的把下面的函数声明认为是信号并且给这些信号函数自动的生成函数定义)此时就会把signals下面的函数声明当信号并给信号自动生成对应的函数定义。下面再把信号和槽关联起来下面测试一下没反应因为此时信号和槽建立连接不代表信号发出来了。那这个自定义的信号如何触发呢Qt内置的信号都不需要手动通过代码来触发用户在GUI进行某些操作就会自动触发对应信号(发射信号的代码已经内置到qt框架中了)。我们自定义的要自己emit(发射)定义的信号发送的信号的操作可以在任何合适的代码中不一定在构造函数里。比如这样(ui中创建按钮和信号)此时是这样一个过程其实现在qt5中emit什么都没有做真正的操作都包含在mySignal内部生成的函数定义了不加emit信号也是可以的发出去的(建议加)3.带参数的信号和槽我们前面写的信号和槽都是不带参数的信号函数和槽函数其实也是可以带参数的。当信号带有参数的时候槽的参数必须和信号参数一致。此时发射信号的时候就可以给信号函数传递实参与之对应的这个参数就会被传递到对应的槽函数中此时就可以起到让信号给槽传参的效果了。下面加个参数看看这里的参数必须一致。一致主要是要求类型个数如果不一致也可以不一致的时候要求信号的参数的个数必须要比槽的参数的个数要更多。进一步完善实现代码此时信号的实参会被传到槽函数的实参里面。下面测试一下传参可以起到复用代码的效果有多个逻辑逻辑上整体一致但是涉及到的数据不同就可以通过函数参数来复用代码并且在不同场景中传入不同的参数即可。比如多加一个按钮给第二个按钮也设置槽函数继续完善现在信号是同一个信号槽也是同一个槽但是可以通过参数达成不同的效果qt中很多内置的信号也只带有参数的这些参数不是我们自己传递的。下面再看看参数个数不一致的情况现在信号的参数多槽的参数少。继续补齐此时虽然槽函数只有一个参数但不影响程序的正常运行。测试发现正确所以信号函数的参数个数超过了槽函数的参数个数此时是可以正常使用的。下面反过来试一试下面再次去测试一下有很多报错所以信号函数的参数个数少于槽函数的参数个数此时代码无法编译通过。直观的思考应该是要求信号的参数个数和槽的参数个数严格一致此处为啥允许信号的参数比槽的参数多呢一个槽函数有可能会绑定多个信号如果我们严格要求参数个数一致就意味着信号绑定到槽的要求就变高了。换而言之当下这样的规则就允许信号和槽之间的绑定更灵活了更多的信号可以绑定到这个槽函数上了。个数不一致槽函数就会按照参数顺序拿到信号的前N个参数至少需要确保槽函数的每个参数都是有值的。要求信号给槽的参数可以有富裕但不能少。再看一下参数类型的影响下面测试一下同样编译报错。因此带有参数的信号要求信号的参数和槽的参数一致类型和个数满足要求(信号的参数个数要多于槽的参数个数)。再介绍一个和信号槽与关系的部分在widget.h中针对Widge类进行声明里面有个这样的宏Qt中如果要让某个类能够使用信号槽(可以在类中定义信号和槽)则必须要在类最开始的地方写下Q_OBJECT宏这是Qt中的硬性规定。如果不加这个宏这个类在编译的时候也会出错。4.信号与槽的断开我们可以使用disconnect来断开信号槽的连接disconnect使用的方式和connect是非常类似的。disconnect用的比较少大部分情况下把信号和槽连上后就不必管了主动断开往往是把信号重新绑定到另一个槽函数上。下面演示一下先拖动一个按钮继续完善此时点击按钮窗口标题发生改变然后再用另一个按钮切换槽函数然后来完善下面演示此时点第一个按钮标题改变再点第二个按钮后再点第一个标题又改变。如果没有disconnect就会构成一个信号绑定两个槽函数触发信号的时候两个槽函数都会执行。5.使用lambda表达式定义槽函数定义槽函数的时候也是可以使用lambda表达式的。很多编程语言都支持这个语法糖它本质就是一个匿名函数主要应用在回调函数的场景中。下面结合代码看一下效果下面运行一下说明槽函数可以顺利执行了。现在我想点击按钮的时候把按钮的位置移动一下怎么办呢发现此时用的时候说button按钮没有定义。因为lambda表达式是一个回调函数这个函数无法直接获取到上层作用域中的变量的。当前button定义是在9~20这个作用域而lambda是自成一个体系获取不到外面的变量。lambda为了解决上述问题引入“变量捕获”语法通过变量捕获获取到外层作用域中的变量在方括号中下手此时相当于让lambda表达式能够把外层作用域中的button变量名捕获到lambda表达式里面这时候就可以在lambda表达式里面使用了类似的比如想在lambda表达式里面对this进行操作就可以捕获一个this比如移动一下窗口此时运行可以看到按钮和窗口位置都会发生移动。如果当前lambda里面想使用更多的外层变量咋办可以写作[]这个写法的含义就是把上层作用域中所有的变量名都给捕获进来后续如果我们对应的槽函数比较简单而且是一次性使用的就会经常写作这种lambda的形式。另外也要确认捕获到lambda内部的变量是有意义的回调函数执行时机是不确定的(用户啥时候点击按钮不知道的)无论何时用户点击了按钮捕获到的变量都能正确使用(不要用的时候已经被释放了)。由于此处button是new出来的变量生命周期跟随整个窗口(挂到对象树上窗口关闭才会释放)这个东西就可以在后面随时使用了。类似的this指向的对象widget这个变量是在main函数结束销毁main函数结束说明进程结束了只要进程不结束widget就可用this也就可用了。lambda除了可以按照值的方式来捕获变量[]还可以按照引用的方式来捕获[]但很少这样写捕获到的变量一般就是各种控件的指针。指针变量按照值传递或者引用来传递都无所谓如果按引用还得更关注这个引用的变量本身的生命周期。lambda语法是c11中引用的对于Qt5及其更高版本默认就是按照c11来编译的。如果使用Qt4或者更老的版本就需要手动在.pro文件中加上c11的编译选项