news 2026/8/14 13:55:52

1.3 Qt中的信号槽

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
1.3 Qt中的信号槽

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); // error

Qt5处理方式错误原因分析:

上边的写法之所以错误是因为这个类中信号槽都是重载过的, 信号和槽都是通过函数名去关联函数的地址, 但是这个同名函数对应两块不同的地址, 一个带参, 一个不带参, 因此编译器就不知道去关联哪块地址了, 所以如果我们在这种时候通过以上方式进行信号槽连接, 编译器就会报错。

解决方案:

我们可以通过定义函数指针的方式指定出函数的具体参数,这样就可以确定函数的具体地址了。
定义函数指针指向重载的某个信号或者槽函数,在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表达式是C++11最重要也是最常用的特性之一,是现代编程语言的一个特点,简洁,提高了代码的效率并且可以使程序更加灵活,Qt是完全支持c++语法的, 因此在Qt中也可以使用Lambda表达式。

5.1 语法格式

Lambda表达式就是一个匿名函数, 语法格式如下:

[capture](params) opt -> ret {body;}; - capture: 捕获列表 - params: 参数列表 - opt: 函数选项 - ret: 返回值类型 - body: 函数体 // 示例代码->匿名函数的调用: int ret = [](int a) -> int { return a+1; }(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 a+1; }(100); // 100是传递给匿名函数的参数

在Lambda表达式的捕获列表中也就是 []内部添加不同的关键字, 就可以在函数体中使用外部变量了。

// 在匿名函数外部定义变量 int a=100, b=200, c=300; // 调用匿名函数 [](){ // 打印外部变量的值 qDebug() << "a:" << a << ", b: " << b << ", c:" << c; // error, 不能使用任何外部变量 } [&](){ qDebug() << "hello, 我是一个lambda表达式..."; qDebug() << "使用引用的方式传递数据: "; qDebug() << "a+1:" << a++ << ", b+c= " << b+c; }(); // 值拷贝的方式使用外部数据 [=](int m, int n)mutable{ qDebug() << "hello, 我是一个lambda表达式..."; qDebug() << "使用拷贝的方式传递数据: "; // 拷贝的外部数据在函数体内部是只读的, 如果不添加 mutable 关键字是不能修改这些只读数据的值的 // 添加 mutable 允许修改的数据是拷贝到函数内部的副本, 对外部数据没有影响 qDebug() << "a+1:" << a++ << ", b+c= " << b+c; qDebug() << "m+1: " << ++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::connect

QObject *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.类必须公有继承 QObject(QWidget、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++类

命名为GirlFriend,Base 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按钮的时候就会触发clicked,clicked只要一被处罚,就会调用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_girl(GirlFriend)发出hungry信号
信号接收者2m_me(Me)接收hungry,执行eat
最终动作Me::eat()输出"我带你去吃麻辣烫"

两条信号槽链

代码中实际建立了三条连接

连接1:m_girl::hungry → m_me::eat (核心逻辑:饿 → 吃饭) 连接2:ui->hungry::clicked → MainWindow::hungrySlot (按钮 → 中转站) 连接3:ui->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按钮之后,会调用一个槽函数hungrySlot,hungrySlot中发射信号可以发射一个也可以发射多个

此时有两条数据

问题:

当Qt4设置的信号槽有参数时,Qt5的代码会报错

这个错误是因为我们在程序中进行了信号槽的重载

hungry是一个函数名,这个函数名指向一块唯一的地址,但是现在叫做hungry的地址从一块变成了两块,叫做eat的地址也从一块变成了两块,hungry和eat不知道选择带参的函数地址还是不带参的函数地址,这是一个语法错误

有两种解决方法:

一种是使用Qt4的连接方式

另外一种需要去外部定义一个函数指针,通过函数指针进行声明,声明我们指向的这个函数的地址是带参的还是不带参的,如果是带参是带几个参数

返回值是void的类型,第一个括号是类型名,声明是GirlFriend,因为hungry属于GirlFriend类,因此这个指针也属于GirlFriend类,::后面是指针,因此要加*,然后起名girl1,第二个括号传参数,如果要调用无参的函数就不用传参,需要调用带参函数就传入指定的参数

得到4条数据,点击hungry按钮后,发射两个信号,一个带参一个不带参,这两个信号被发射出去之后有对应着不同的处理动作

对于第三条带你吃海鲜,传入的信号有参数,但是槽函数可以没有参数,表示槽函数对接收的信号不做任何处理,槽函数的参数个数可以少于等于信号的参数个数,不可以大于信号的参数个数

不推荐有Qt4的方式,因为会进行宏替换,宏替换的过程中不会做错误检查

7.Lambda表达式

Lambda表达式是从C++11继承过来的一个特性

[capture](params) opt -> ret {body;}; - capture: 捕获列表 - params: 参数列表 - opt: 函数选项 - ret: 返回值类型 - body: 函数体 // 示例代码->匿名函数的调用: int ret = [](int a) -> int { return a+1; }(100);

匿名函数的定义

因为函数没有名字,因此在调用的时候把它的整体作为一个函数名,在最后加上一个参数列表

这里两个括号都是参数列表 前面的是形参,后面的是实参

匿名函数的组织结构:

第一部分是如何捕捉外部数据,从小范围看,当前这个匿名函数位于MainWindow的构造函数中,意味着这个匿名对象可以捕捉构造函数里的一些局部变量

在匿名函数体外部定义几个变量,如果想在匿名函数体内部使用外部的变量就必须加上捕捉方式

引用方式

注意调用时最后面必须加()

值拷贝方式,匿名函数体内部的变量是只读的,因此会报错,解决这个问题是在opt的位置加上mutable关键字,mutable的意思是允许在函数体内部来使用只读数据

函数体内部的数据a是拷贝的,对这个a操作并不会影响原来的数据

有参数的方式,上面的括号是形参,用来接收数据的,下面的括号里的参数是实参,需要传入具体的数据

lambda表达式的返回值

opt选项

在qt中使用lambda表达式

在qt中如果要进行信号槽的连接,发出一个信号,如果没有标准槽函数,就必须自己写一个自定义类型的槽函数,有了lambda表达式之后就可以直接写一个匿名函数作为槽函数

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!