1. 信号和槽概述信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽实际就是观察者模式(发布-订阅模式)。当某个事件发生之后比如按钮检测到自己被点击了一下它就会发出一个信号signal。这种发出是没有目的的类似广播。如果有对象对这个信号感兴趣它就会使用连接connect函数意思是将想要处理的信号和自己的一个函数称为槽slot绑定来处理这个信号。也就是说当信号发出时被连接的槽函数会自动被回调。这就类似观察者模式当发生了感兴趣的事件某一个操作就会被自动触发。1.1 信号的本质信号是由于用户对窗口或控件进行了某些操作导致窗口或控件产生了某个特定事件这时候Qt对应的窗口类会发出某个信号以此对用户的挑选做出反应。因此根据上述的描述我们得到一个结论信号的本质就是事件比如* 按钮单击、双击* 窗口刷新* 鼠标移动、鼠标按下、鼠标释放* 键盘输入那么在Qt中信号是通过什么形式呈现给使用者的呢* 我们对哪个窗口进行操作, 哪个窗口就可以捕捉到这些被触发的事件。* 对于使用者来说触发了一个事件我们就可以得到Qt框架给我们发出的某个特定信号。* 信号的呈现形式就是函数 也就是说某个事件产生了 Qt框架就会调用某个对应的信号函数 通知使用者。在QT中信号的发出者是某个实例化的类对象对象内部可以进行相关事件的检测。1.2 槽的本质在Qt中槽函数是一类特殊的功能的函数在编码过程中也可以作为类的普通成员函数来使用。之所以称之为槽函数是因为它们还有一个职责就是对Qt框架中产生的信号进行处理。举个简单的例子女朋友说“我肚子饿了”于是我带她去吃饭。上边例子中相当于女朋友发出了一个信号 我收到了信号并其将其处理掉了。实例对象角色描述女朋友信号发出者信号携带的信息我饿了我信号接收者处理女朋友发射的信号带她去吃饭在Qt中槽函数的所有者也是某个类的实例对象。1.3 信号和槽的关系在Qt中信号和槽函数都是独立的个体本身没有任何联系但是由于某种特性需求我们可以将二者连接到一起好比牛郎和织女想要相会必须要有喜鹊为他们搭桥一样。在Qt中我们需要使用QOjbect类中的connect函数进二者的关联。连接信号和槽的connect()函数原型如下, 其中PointerToMemberFunction是一个指向函数地址的指针QMetaObject::Connection QObject::connect( const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type Qt::AutoConnection); 参数: - sender: 发出信号的对象 - signal: 属于sender对象, 信号是一个函数, 这个参数的类型是函数 指针, 信号函数地址 - receiver: 信号接收者 - method: 属于receiver对象, 当检测到sender发出了signal信号, receiver对象调用method方法信号发出之后的处理动作 // connect函数相当于做了信号处理动作的注册 // 调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的method也不会被调用 // method槽函数本质是一个回调函数, 调用的时机是信号产生之后, 调用是Qt框架来执行的 // connect中的sender和recever两个指针必须被实例化了, 否则conenct不会成功 // 参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下: connect(const QObject *sender, QObject::signal, const QObject *receiver, QObject::method);使用connect()进行信号槽连接的注意事项:1.connect函数相对于做了信号处理动作的注册2.调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的method也不会被调用3.method槽函数本质是一个回调函数, 调用的时机是信号产生之后, 调用是Qt框架来执行的4.connect中的sender和recever两个指针必须被实例化了, 否则conenct不会成功2. 标准信号槽使用2.1 标准信号/槽在Qt提供的很多标准类中都可以对用户触发的某些特定事件进行检测, 因此当用户做了这些操作之后, 事件被触发类的内部就会产生对应的信号, 这些信号都是Qt类内部自带的, 因此称之为标准信号。同样的在Qt的很多类内部为我们提供了很多功能函数并且这些函数也可以作为触发的信号的处理动作有这类特性的函数在Qt中称之为标准槽函数。系统自带的信号和槽通常如何查找呢这个就需要利用帮助文档了在帮助文档中比如我们上面的按钮的点击信号在帮助文档中输入QPushButton首先我们可以在Contents中寻找关键字 signals信号的意思但是我们发现并没有找到这时候我们应该看当前类从父类继承下来了哪些信号因此我们去他的父类QAbstractButton中就可以找到该关键字点击signals索引到系统自带的信号有如下几个2.2 使用功能实现 点击窗口上的按钮, 关闭窗口功能分析:- 按钮: 信号发出者 - QPushButton 类型 - 窗口: 信号的接收者和处理者 - QWidget 类型需要使用的标准信号槽函数// 单击按钮发出的信号 [signal] void QAbstractButton::clicked(bool checked false) // 关闭窗口的槽函数 [slot] bool QWidget::close();对于上边的需求只需要一句代码, 只需要写一句代码就能实现了// 单击按钮关闭窗口 connect(ui-closewindow, QPushButton::clicked, this, MainWindow::close);connect()操作一般写在窗口的构造函数中, 相当于在事件产生之前在qt框架中先进行注册, 这样在程序运行过程中假设产生了按钮的点击事件, 框架就会调用信号接收者对象对应的槽函数了, 如果信号不产生, 槽函数也就一直不会被调用。3. 自定义信号槽使用Qt框架提供的信号槽在某些特定场景下是无法满足我们的项目需求的因此我们还设计自己需要的的信号和槽同样还是使用connect()对自定义的信号槽进行连接。如果想要在QT类中自定义信号槽, 需要满足一些条件, 并且有些事项也需要注意:1.要编写新的类并且让其继承Qt的某些标准类2.这个新的子类必须从QObject类或者是QObject子类进行派生3.在定义类的头文件中加入 Q_OBJECT 宏// 在头文件派生类的时候首先像下面那样引入Q_OBJECT宏 class MyMainWindow : public QWidget { Q_OBJECT ...... }3.1 自定义信号在Qt中信号的本质是事件, 但是在框架中也是以函数的形式存在的, 只不过信号对应的函数只有声明, 没有定义。如果Qt中的标准信号不能满足我们的需求可以在程序中进行信号的自定义当自定义信号对应的事件产生之后认为的将这个信号发射出去即可其实就是调用一下这个信号函数。自定义信号的要求和注意事项:1. 信号是类的成员函数2. 返回值是 void 类型3. 信号的名字可以根据实际情况进行指定4. 参数可以随意指定, 信号也支持重载5. 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字6. 信号函数只需要声明, 不需要定义(没有函数体实现)7. 在程序中发送自定义信号: 发送信号的本质就是调用信号函数- 习惯性在信号函数前加关键字: emit- emit只是显示的声明一下信号要被发送, 没有特殊含义- 底层 emit #define emit// 举例: 信号重载 // Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以) class Test : public QObject { Q_OBJECT signals: void testsignal(); // 参数的作用是数据传递, 谁调用信号函数谁就指定实参 // 实参最终会被传递给槽函数 void testsignal(int a); };3.2 自定义槽槽函数就是信号的处理动作在Qt中槽函数可以作为普通的成员函数来使用。如果标准槽函数提供的功能满足不了需求可以自己定义槽函数进行某些特殊功能的实现。自定义槽函数和自定义的普通函数写法是一样的。自定义槽的要求和注意事项:1. 返回值是 void 类型2. 槽也是函数, 因此也支持重载- 槽函数需要指定多少个参数, 需要看连接的信号的参数个数- 槽函数的参数是用来接收信号发送的数据的, 信号发送的数据就是信号的参数- 举例:- 信号函数: void testsig(int a, double b); - 槽函数: void testslot(int a, double b);- 总结:- 槽函数的参数应该和对应的信号的参数个数, 类型一一对应- 信号的参数可以大于等于槽函数的参数个数 信号传递的数据被忽略了- 信号函数: void testsig(int a, double b); - 槽函数: void testslot(int a);3. Qt中槽函数的类型是多样的- 类的成员函数- 全局函数- 静态函数- lambda表达式(匿名函数)4. 槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写)- public slots:- private slots:– 这样的槽函数不能在类外部被调用- protected slots:– 这样的槽函数不能在类外部被调用// 槽函数书写格式举例 // 类中的这三个函数都可以作为槽函数来使用 class Test : public QObject { public: void testSlot(); static void testFunc(); public slots: void testSlot(int id); };根据特定场景自定义信号槽:还是上边的场景:女朋友说“我肚子饿了”于是我带她去吃饭。// 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); public slots: // 槽函数 void eatMeal(); // 不能知道信号发出者要吃什么 void eatMeal(QString msg); // 可以知道信号发出者要吃什么 };4. 信号槽拓展4.1 信号槽使用拓展* 一个信号可以连接多个槽函数, 发送一个信号有多个处理动作* 需要写多个connect()连接* 槽函数的执行顺序是随机的, 和connect函数的调用顺序没有关系* 信号的接收者可以是一个对象, 也可以是多个对象* 一个槽函数可以连接多个信号, 多个不同的信号, 处理动作是相同的* 写多个connect()就可以* 信号可以连接信号* 信号接收者可以不出来接收的信号, 继续发出新的信号 - 传递了数据, 并没有进行处理connect(const QObject *sender, QObject::signal, const QObject *receiver, QObject::siganl-new);信号槽是可以断开的disconnect(const QObject *sender, QObject::signal, const QObject *receiver, QObject::method);4.2 信号槽的连接方式* Qt5的连接方式// 语法: QMetaObject::Connection QObject::connect( const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type Qt::AutoConnection); // 信号和槽函数也就是第2,4个参数传递的是地址, 编译器在编译过程中会对数据的正确性进行检测 connect(const QObject *sender, QObject::signal, const QObject *receiver, QObject::method);Qt4的连接方式这种旧的信号槽连接方式在Qt5中是支持的, 但是不推荐使用, 因为这种方式在进行信号槽连接的时候, 信号槽函数通过宏SIGNAL和SLOT转换为字符串类型。因为信号槽函数的转换是通过宏来进行转换的因此传递到宏函数内部的数据不会被进行检测 如果使用者传错了数据编译器也不会报错但实际上信号槽的连接已经不对了只有在程序运行起来之后才能发现问题而且问题不容易被定位。// Qt4的信号槽连接方式 [static] QMetaObject::Connection QObject::connect( const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type Qt::AutoConnection); connect(const QObject *sender,SIGNAL(信号函数名(参数1, 参数2, ...)), const QObject *receiver,SLOT(槽函数名(参数1, 参数2, ...)));Qt4中声明槽函数必须要使用 slots 关键字, 不能省略。应用举例场景描述 - 我肚子饿了, 我要吃东西。 分析 - 信号的发出者是我自己, 信号的接收者也是我自己我们首先定义出一个Qt的类。class Me : public QObject { Q_OBJECT // Qt4中的槽函数必须这样声明, qt5中的关键字 slots 可以被省略 public slots: void eat(); void eat(QString somthing); signals: void hungury(); void hungury(QString somthing); }; // 基于上边的类写出解决方案 // 处理如下逻辑: 我饿了, 我要吃东西 // 分析: 信号的发出者是我自己, 信号的接收者也是我自己 Me m; // Qt4处理方式 connect(m, SIGNAL(hungury()), m, SLOT(eat())); connect(m, SIGNAL(hungury(QString)), m, SLOT(eat(QString))); // Qt5处理方式 connect(m, Me::hungury, m, Me::eat); // errorQt5处理方式错误原因分析上边的写法之所以错误是因为这个类中信号槽都是重载过的, 信号和槽都是通过函数名去关联函数的地址, 但是这个同名函数对应两块不同的地址, 一个带参, 一个不带参, 因此编译器就不知道去关联哪块地址了, 所以如果我们在这种时候通过以上方式进行信号槽连接, 编译器就会报错。解决方案我们可以通过定义函数指针的方式指定出函数的具体参数这样就可以确定函数的具体地址了。定义函数指针指向重载的某个信号或者槽函数在connect函数中将函数指针名字作为实参就可以了。举例void (Me::*func1)(QString) Me::eat; -- func1指向带参的槽函数 void (Me::*func2)() Me::hungury; -- func2指向不带参的信号Qt正确的处理方式:// 定义函数指针指向重载的某一个具体的槽函数地址 void (Me::*myslot)(QString) Me::eat; // 定义函数指针指向重载的某一个具体的信号地址 void (Me::*mysignal)(QString) Me::hungury; // 使用定义的函数指针完成信号槽的连接 connect(m, mysignal, m, myslot);总结* Qt4的信号槽连接方式因为使用了宏函数, 宏函数对用户传递的信号槽不会做错误检测, 容易出bug* Qt5的信号槽连接方式, 传递的是信号槽函数的地址, 编译器会做错误检测, 减少了bug的产生* 当信号槽函数被重载之后, Qt4的信号槽连接方式不受影响* 当信号槽函数被重载之后, Qt5中需要给被重载的信号或者槽定义函数指针5. Lambda表达式Lambda表达式是C11最重要也是最常用的特性之一是现代编程语言的一个特点简洁提高了代码的效率并且可以使程序更加灵活Qt是完全支持c语法的 因此在Qt中也可以使用Lambda表达式。5.1 语法格式Lambda表达式就是一个匿名函数 语法格式如下[capture](params) opt - ret {body;}; - capture: 捕获列表 - params: 参数列表 - opt: 函数选项 - ret: 返回值类型 - body: 函数体 // 示例代码-匿名函数的调用: int ret [](int a) - int { return a1; }(100);关于Lambda表达式的细节介绍:1. 捕获列表: 捕获一定范围内的变量* [] - 不捕捉任何变量* [] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)* [] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)* 拷贝的副本在匿名函数体内部是只读的* [, foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo* [bar] - 按值捕获 bar 变量, 同时不捕获其他变量* [bar] - 按值捕获 bar 变量, 同时不捕获其他变量* [this] - 捕获当前类中的this指针* 让lambda表达式拥有和当前类成员函数同样的访问权限* 如果已经使用了 或者 , 默认添加此选项2. 参数列表: 和普通函数的参数列表一样3. opt 选项 -- 可以省略* mutable: 可以修改按值传递进来的拷贝注意是能修改拷贝而不是值本身* exception: 指定函数抛出的异常如抛出整数类型的异常可以使用throw();4. 返回值类型:* 标识函数返回值的类型当返回值为void或者函数体中只有一处return的地方此时编译器可以自动推断出返回值类型时这部分可以省略5. 函数体:* 函数的实现这部分不能省略但函数体可以为空。5.2 定义和调用// 匿名函数的定义, 程序执行这个匿名函数是不会被调用的 [](){ qDebug() hello, 我是一个lambda表达式...; }; // 匿名函数的定义调用: int ret [](int a) - int { return a1; }(100); // 100是传递给匿名函数的参数在Lambda表达式的捕获列表中也就是 []内部添加不同的关键字, 就可以在函数体中使用外部变量了。// 在匿名函数外部定义变量 int a100, b200, c300; // 调用匿名函数 [](){ // 打印外部变量的值 qDebug() a: a , b: b , c: c; // error, 不能使用任何外部变量 } [](){ qDebug() hello, 我是一个lambda表达式...; qDebug() 使用引用的方式传递数据: ; qDebug() a1: a , bc bc; }(); // 值拷贝的方式使用外部数据 [](int m, int n)mutable{ qDebug() hello, 我是一个lambda表达式...; qDebug() 使用拷贝的方式传递数据: ; // 拷贝的外部数据在函数体内部是只读的, 如果不添加 mutable 关键字是不能修改这些只读数据的值的 // 添加 mutable 允许修改的数据是拷贝到函数内部的副本, 对外部数据没有影响 qDebug() a1: a , bc bc; qDebug() m1: m , n: n; }(1, 2);笔记1.信号槽的本质信号的本质是事件信号是对产生的事件的描述信号的呈现形式是函数信号是由窗口发出的意味着窗口是一个被实例化的对象发出的只有这个对象被实例化了它才能检测到相关事件的发生槽是一个处理动作只要是有处理动作这个处理动作也需要有一个载体因此这个槽函数也应该属于某个对象通过connect()函数进行连接QMetaObject::Connection QObject::connect( const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type Qt::AutoConnection);返回值类型QMetaObject::Connection函数名QObject::connectQObject *sender是const QObject*类型的指针指向信号发出者的对象的地址PointerToMemberFunction signal是一个函数指针指向要发出的那个信号的函数地址const QObject *receiver是const QObject*类型的指针指向信号接收者的对象的地址PointerToMemberFunction method是一个槽函数指针指向槽函数地址connect函数的作用是进行注册当某一个对象sender发出一个信号signal后有另外一个对象receiver进行method处理。槽函数是由qt框架调用的connect只是把这个事件注册给qt框架qt框架知道有这个槽函数之后就会一直检测sender对象发出的signal信号当检测到信号的时候qt框架就会调用receiver对象的method方法对发出的信号进行处理这个机制的本质是一个回调所谓的回调就是我们只需要编写函数的处理动作不需要去管理函数的调用框架会帮助我们进行调用2.标准信号槽的使用标准信号槽就是qt给我们提供的一些信号和槽这些信号和槽都在qt提供的一些标准类里我们只需要对标准类进行查询就可以进行使用如何基于帮助文档进行类的查询signals是系统提供的标准信号Public Slots是这个类提供的标准槽函数clicked相当于被点了一下pressed相当于被按下了released相当于被释放了toggled相当于按着不松手按钮是信号发出者通过QPushButton发出信号窗口是信号接收者和处理者负责接收信号并做出相应的操作通过Qwidget找到槽函数单击按钮发出clicked信号close()槽函数相应并关闭窗口通过connect将两者连接起来在ui文件中找到Push Button按钮并拖到页面中双击可以修改名字在右边可以找到属性和值这个值就是当前按钮的名字这个名字就是用来指向这个按钮对象的指针通过这个指针操作对象这个按钮的类型是QPushButton类型修改值为closeBtn在上方的视图中可以找到对象查看器和属性编辑器在mainwindow.cpp源文件中new出来了Mainwindow类型的对象对象名叫ui通过connect函数把信号和槽连接到一起调用connect函数只要qt框架检测到closeBtn按钮被按下了它就会调用MainWindow类中的close方法这个方法是从父类继承下来的点击关闭窗口按钮窗口就会相应然后关闭对connect函数来说只有clicked这个动作信号发出了它才会相应然后执行close槽函数如果没有动作发生它就永远不会执行3.在Qt中自定义信号槽的规则Qt 框架提供的信号槽在某些特定场景下是无法满足我们的项目需求的因此我们还设计自己需要的信号和槽同样还是使用connect()对自定义的信号槽进行连接。如果想要使用自定义的信号槽首先要编写新的类并且让其继承Qt的某些标准类 我们自己编写的类想要在Qt中使用信号槽机制那么必须要满足的如下条件 - 这个类必须从QObject类或者是其子类进行派生 - 在定义类的头文件中加入 Q_OBJECT 宏 // 在头文件派生类的时候首先像下面那样引入 Q_OBJECT 宏 class MyMainWindow : public QWidget { Q_OBJECT };Q_OBJECT 宏核心作用1.触发 Qt MOC 元对象编译器Qt 的信号槽不是 C 原生语法靠 MOC 工具扫描头文件识别Q_OBJECT宏自动生成元对象代码信号发射、槽函数调用、类型映射、运行时反射等底层实现。 不加这个宏自定义信号、槽函数完全无法生效。2.赋予类这些能力支持自定义signals:信号声明支持slots:槽函数定义运行时类型识别、动态属性、元信息查询connect 信号槽绑定机制正常工作使用硬性条件1.类必须公有继承 QObjectQWidget、QMainWindow 都是 QObject 子类均可2.类的头文件内部第一行写上Q_OBJECT;3.写完后必须执行qmake → 重新构建项目不然 MOC 不会生成对应编译文件会直接报信号未定义编译错误自定义信号 7 条硬性规则要求 1. 信号是类的成员函数 2. 返回值是 void 类型 3. 信号的名字可以根据实际情况进行指定 4. 参数可以随意指定信号也支持重载 5. 信号需要使用 signals 关键字进行声明使用方法类似于public等关键字 6. 信号函数只需要声明不需要定义(没有函数体实现) 7. 在程序中发送自定义信号发送信号的本质就是调用信号函数 - 习惯性在信号函数前加关键字emit - emit只是显示的声明一下信号要被发送没有特殊含义 - 底层 emit #define emit 空宏无实际代码信号重载// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以) class Test : public QObject { Q_OBJECT // 必备宏 signals: void testsignal(); // 参数的作用是数据传递谁调用信号函数谁就指定实参 // 实参最终会被传递给槽函数 void testsignal(int a); };核心知识点拆解1.信号本质信号就是声明在signals:区域、无函数体、返回值固定void的成员函数不需要自己写 cpp 实现MOC 工具自动生成发射逻辑。2.emit 关键字Qt 语法糖本质是空宏定义emit testsignal(10);等价于直接调用testsignal(10);写上只是方便阅读代码一眼看出此处是触发信号。3.信号重载同一个信号名可以定义不同参数版本后续 connect 绑定的时候需要用函数指针区分重载版本否则编译器会歧义报错。4.数据传递逻辑信号携带的参数会完整转发给绑定的槽函数实现对象之间的数据交互比如女朋友发送void hungry(QString msg)把 “我饿了” 传给接收方的槽。5.必备前提千万不能忘公有继承QObject类内部第一行添加Q_OBJECT宏修改头文件后执行 qmake 重新构建项目发送信号示例Test t; // 触发无参信号 emit t.testsignal(); // 触发带int参数的重载信号 emit t.testsignal(666);4.自定义信号槽的使用举例一共是两个对象一个是女朋友一个是我因此需要定义两个类使用自定义信号槽还需要自己定义一个新的类class GirlFriend class Me添加新文件这个类不涉及任何窗口的操作因此可以直接定义为c类命名为GirlFriendBase class一定要选择QObject类或者其子类这样才能够使用qt中的信号机制选择QObject类后会自动生成这个类的头文件并添加宏定义创建Me这个类和操作上述相同在GirlFriend.h头文件的类定义中添加signals关键字声明函数函数返回值一定要是void在Me.h头文件中设置槽函数slots可以自己写一个public slots定义也可以直接在类的构造函数的public下写定义槽函数不需要传参因为信号里面就没有参数同时返回值是void在Me.cpp中实现函数的功能让两个对象同时出现在同一个窗口中让这两个类的对象都添加到mainwindow.h中作为当前类的一个成员变量以指针的方式定义对象成员变量在私有成员中在mainwindow.cpp源文件中初始化两个对象的指针然后将这两个对象进行关联通过connect函数这样做有一个问题hungry信号是自定义的它不能够由框架去发送因此我们需要在指定的时机自己去发射这个自定义的信号可以在当前窗口mainwindow中添加一个按钮点一下这个按钮表示女朋友饿了饿了就会发出信号信号发出就去响应然后就去吃饭添加控件要记得修改一下object的名字这样方便理解和查看通过这个指针就能够访问到这个按钮的实际对象按下按钮后对应一个处理动作因此需要在mainwindow这个类中添加一个槽函数hungrySlot();在源文件中实现这个函数再用connect将添加的槽函数和按钮的信号进行关联当我们按下窗口的hungry按钮的时候就会触发clickedclicked只要一被处罚就会调用hungrySlot函数hungrySlot函数被调用之后就会发射一个hungry()的操作hungry信号被发射出去后就被框架检测到然后m_me这个对象就能够接受这个信号然后执行eat操作运行一下执行流程用户点击 UI 上的 hungry 按钮 ↓ QPushButton::clicked 信号Qt 内置信号 ↓ MainWindow::hungrySlot 槽函数你写的中间槽 ↓ emit m_girl-hungry()发出自定义信号 ↓ GirlFriend::hungry 信号自定义信号 ↓ Me::eat 槽函数自定义槽 ↓ qDebug() 我带你去吃麻辣烫各个角色的对应角色类/对象作用信号发送者1ui-hungry按钮发出clicked信号信号接收者1/转发者MainWindowthis接收clicked执行hungrySlot信号发送者2m_girlGirlFriend发出hungry信号信号接收者2m_meMe接收hungry执行eat最终动作Me::eat()输出我带你去吃麻辣烫两条信号槽链代码中实际建立了三条连接连接1m_girl::hungry → m_me::eat 核心逻辑饿 → 吃饭 连接2ui-hungry::clicked → MainWindow::hungrySlot 按钮 → 中转站 连接3ui-closeBtn::clicked → MainWindow::close 按钮 → 关闭窗口5.信号槽的拓展使用接收者在进行信号接收的时候必须进行一次connect操作GirlFriend发出一个hungry信号hungry信号被me这个对象处理了我们也可以让当前mainwindow把这个信号处理掉在mainwindow的类中添加槽函数void eatSlot();源函数中实现connect(m_girl,GirlFriend::hungry,this,MainWindow::eatSlot);信号可以连接信号在主窗口中有一个hungry按钮在这个hungry按钮对应的函数中定义了一个自定义信号m_girl-hungry ();信号连接信号之后就可以把这个操作简化掉原来是信号连接槽函数现在改成信号连接信号原来是接收到信号之后进行处理现在改成接收到信号之后继续发射信号调用disconnect可以断开信号槽6.信号槽的两种连接方式Qt5和Qt4的区别主要在connect函数的第二个和第四个参数在Qt5需要指定信号和槽的函数地址在Qt4中指定的是一个char*类型的指针char*类型的指针是通过SIGNAL宏和SLOT宏转换得到的Qt4需要把信号函数的名字和参数列表写到SIGNAL宏里槽函数也要指定它的名字和参数列表写到SLOT宏里SIGNAL宏和SLOT宏就可以把信号函数和槽函数转换成一个char*类型的参数然后就可以连接成功举例发出一个信号做了三次连接执行了三个对应的处理操作这就是一个信号对应多个槽函数对hungry函数进行重载void hungry(QString msg);在槽函数中也要重载信号中有参数槽函数中也要有参数会发现只有一条数据输出当我们点击hungry按钮之后会调用一个槽函数hungrySlothungrySlot中发射信号可以发射一个也可以发射多个此时有两条数据问题当Qt4设置的信号槽有参数时Qt5的代码会报错这个错误是因为我们在程序中进行了信号槽的重载hungry是一个函数名这个函数名指向一块唯一的地址但是现在叫做hungry的地址从一块变成了两块叫做eat的地址也从一块变成了两块hungry和eat不知道选择带参的函数地址还是不带参的函数地址这是一个语法错误有两种解决方法一种是使用Qt4的连接方式另外一种需要去外部定义一个函数指针通过函数指针进行声明声明我们指向的这个函数的地址是带参的还是不带参的如果是带参是带几个参数返回值是void的类型第一个括号是类型名声明是GirlFriend因为hungry属于GirlFriend类因此这个指针也属于GirlFriend类后面是指针因此要加*然后起名girl1第二个括号传参数如果要调用无参的函数就不用传参需要调用带参函数就传入指定的参数得到4条数据点击hungry按钮后发射两个信号一个带参一个不带参这两个信号被发射出去之后有对应着不同的处理动作对于第三条带你吃海鲜传入的信号有参数但是槽函数可以没有参数表示槽函数对接收的信号不做任何处理槽函数的参数个数可以少于等于信号的参数个数不可以大于信号的参数个数不推荐有Qt4的方式因为会进行宏替换宏替换的过程中不会做错误检查7.Lambda表达式Lambda表达式是从C11继承过来的一个特性[capture](params) opt - ret {body;}; - capture: 捕获列表 - params: 参数列表 - opt: 函数选项 - ret: 返回值类型 - body: 函数体 // 示例代码-匿名函数的调用: int ret [](int a) - int { return a1; }(100);匿名函数的定义因为函数没有名字因此在调用的时候把它的整体作为一个函数名在最后加上一个参数列表这里两个括号都是参数列表 前面的是形参后面的是实参匿名函数的组织结构第一部分是如何捕捉外部数据从小范围看当前这个匿名函数位于MainWindow的构造函数中意味着这个匿名对象可以捕捉构造函数里的一些局部变量在匿名函数体外部定义几个变量如果想在匿名函数体内部使用外部的变量就必须加上捕捉方式引用方式注意调用时最后面必须加()值拷贝方式匿名函数体内部的变量是只读的因此会报错解决这个问题是在opt的位置加上mutable关键字mutable的意思是允许在函数体内部来使用只读数据函数体内部的数据a是拷贝的对这个a操作并不会影响原来的数据有参数的方式上面的括号是形参用来接收数据的下面的括号里的参数是实参需要传入具体的数据lambda表达式的返回值opt选项在qt中使用lambda表达式在qt中如果要进行信号槽的连接发出一个信号如果没有标准槽函数就必须自己写一个自定义类型的槽函数有了lambda表达式之后就可以直接写一个匿名函数作为槽函数