在 Qt 开发中,connect() 是非常常见的一个函数。
按钮点击、输入框内容变化、定时器触发、网络连接成功、线程之间通信,都经常会看到:
connect(...)
刚开始学习 Qt 时,很多人会觉得这个函数很奇怪:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
为什么有四个参数?
button 是什么?
&QPushButton::clicked 又是什么?
为什么有时候 connect() 只有三个参数?
本文不去罗列 connect() 的所有重载,而是从最常见的使用场景出发,把它真正理解清楚。
一、connect() 到底是干什么的?
先看一个最简单的需求:
点击按钮以后,执行一个函数。
Qt 中的按钮被点击时,会发出一个信号:
QPushButton::clicked
我们希望这个信号发生以后,执行自己的函数:
MainWindow::handleClicked
于是就可以写:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
这句话可以直接理解成:
当
button发出clicked信号时,让MainWindow执行handleClicked()。
所以 connect() 的核心作用其实非常简单:
某个对象
↓
发出信号
↓
触发另一个函数
二、先记住 connect() 最核心的结构
最常见的成员函数连接方式:
connect(sender,
signal,
receiver,
function);
可以理解成四个问题:
谁发信号?
↓
发什么信号?
↓
谁接收?
↓
执行什么函数?
例如:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
对应关系就是:
| 参数 | 含义 |
|---|---|
button | 信号发送者 |
&QPushButton::clicked | 信号 |
this | 接收对象 |
&MainWindow::handleClicked | 接收后执行的函数 |
可以简单画成:
button
│
│ clicked
↓
MainWindow
│
↓
handleClicked()
只要理解了这个关系,connect() 就不难了。
三、为什么要写 &QPushButton::clicked?
第一次看到:
&QPushButton::clicked
很多人会觉得这里的 & 很奇怪。
这里可以先把它理解为:
指定
QPushButton类中的clicked这个成员函数。
同样:
&MainWindow::handleClicked
表示:
指定
MainWindow类中的handleClicked这个成员函数。
因此:
&QPushButton::clicked
和:
&MainWindow::handleClicked
都是在明确告诉编译器:
我要使用哪个类中的哪个成员函数。
在这种 connect() 写法中,& 可以省略,某些情况下写成:
QPushButton::clicked
也能够成立。
不过实际 Qt 代码中通常保留 &:
&QPushButton::clicked
这样写法更加明确,也已经成为现代 Qt 中非常常见的写法。
因此学习 Qt 时,直接记住:
&类名::成员函数
即可,不需要在这里纠结 & 的底层细节。
四、第三个参数为什么经常是 this?
继续看:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
这里的:
this
表示当前的 MainWindow 对象。
因为:
handleClicked()
是 MainWindow 的成员函数,所以 Qt 需要知道:
到底是哪一个
MainWindow对象来执行这个函数?
这里的:
this
就是当前对象。
因此:
this,
&MainWindow::handleClicked
可以理解成:
当前这个
MainWindow对象,收到信号以后执行handleClicked()。
五、普通成员函数通常使用四参数形式
例如:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
private:
void handleClicked();
};
然后:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QPushButton *button = new QPushButton("点击我", this);
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
}
槽函数:
void MainWindow::handleClicked()
{
qDebug() << "按钮被点击了";
}
运行以后:
点击按钮
↓
clicked 信号
↓
handleClicked()
这里的 handleClicked() 并不会在执行 connect() 的时候立即调用。
connect() 做的事情只是:
先把信号和函数连接起来。
等以后真正发生:
clicked
信号时,函数才会执行。
六、connect() 不一定必须有四个参数
这是很多初学者容易忽略的一点。
如果最后连接的不是一个成员函数,而是 Lambda 或普通函数,那么 Qt 提供了三参数形式:
connect(sender,
signal,
functor);
例如:
connect(button,
&QPushButton::clicked,
[]()
{
qDebug() << "按钮被点击了";
});
这里就只有三个参数。
Qt 官方文档将这种形式定义为从信号连接到 functor 的 connect() 重载,Lambda 就属于这种情况。
所以:
connect(button,
&QPushButton::clicked,
[]()
{
qDebug() << "Hello";
});
也是完全正常的 Qt 写法。
七、为什么 Lambda 可以省掉 this?
对比下面两种写法:
四参数
connect(button,
&QPushButton::clicked,
this,
[this]()
{
qDebug() << "Hello";
});
三参数
connect(button,
&QPushButton::clicked,
[this]()
{
qDebug() << "Hello";
});
第二种就是三参数形式。
这里需要注意:
不是简单地说“
this可以省略”。
真正的情况是:
四参数版本:
connect(sender, signal, context, functor);
第三个参数是一个 QObject,它充当 context。
三参数版本:
connect(sender, signal, functor);
没有指定 context。
Qt 官方文档明确说明,三参数这种 context-less connect 中,如果 Lambda 内部使用了其他对象,需要自己保证这些对象在信号触发时仍然有效;官方因此建议在适合的场景下使用带 QObject context 的版本。
所以初学阶段可以这样理解:
成员函数
↓
通常使用四参数
Lambda / 普通函数
↓
可以使用三参数
八、Lambda 中的 [this] 又是什么?
例如:
connect(button,
&QPushButton::clicked,
this,
[this]()
{
ui->label->setText("按钮被点击");
});
这里其实有两个 this:
this,
[this]()
它们作用完全不同。
第一个:
this
是 connect() 的第三个参数。
它告诉 Qt:
这个连接以当前
MainWindow作为 context。
第二个:
[this]
是 Lambda 的捕获列表。
它告诉 C++:
这个 Lambda 要使用当前对象的
this。
例如:
[this]()
{
ui->label->setText("Hello");
}
因为 Lambda 里面访问了:
ui->label
所以需要捕获当前对象。
九、信号有参数怎么办?
信号并不一定没有参数。
例如:
QSlider::valueChanged
会传递一个整数。
可以连接到:
void MainWindow::handleValueChanged(int value)
{
qDebug() << value;
}
完整写法:
connect(slider,
&QSlider::valueChanged,
this,
&MainWindow::handleValueChanged);
可以理解成:
valueChanged(int)
↓
handleValueChanged(int)
例如滑块变化到:
50
相当于:
valueChanged(50)
↓
handleValueChanged(50)
所以写 connect() 时,还需要注意:
信号携带的参数,要能够传给后面的函数。
十、Lambda 也可以接收信号参数
例如:
connect(slider,
&QSlider::valueChanged,
this,
[](int value)
{
qDebug() << "当前值:" << value;
});
这里:
[](int value)
直接接收:
QSlider::valueChanged(int)
传过来的参数。
因此:
QSlider
↓
valueChanged(50)
↓
Lambda(int value)
↓
输出 50
这也是 Lambda 在 Qt 中非常方便的原因。
十一、自定义信号也是一样的
前面使用的是 Qt 自带的:
QPushButton::clicked
但我们也可以自己定义信号。
例如:
class Sender : public QObject
{
Q_OBJECT
signals:
void messageChanged(const QString &message);
};
接收方:
class Receiver : public QObject
{
Q_OBJECT
public:
void handleMessage(const QString &message);
};
连接:
connect(sender,
&Sender::messageChanged,
receiver,
&Receiver::handleMessage);
然后:
emit messageChanged("Hello");
就会触发:
handleMessage("Hello");
这里的逻辑和按钮完全一样:
Sender
↓
messageChanged()
↓
Receiver
↓
handleMessage()
所以无论是 Qt 自带信号还是自己定义的信号,核心思路都是一样的。
十二、实际开发中怎么选择?
如果是一个普通成员函数:
void handleClicked();
通常写:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
如果只是很简单的一小段逻辑,可以使用 Lambda:
connect(button,
&QPushButton::clicked,
this,
[this]()
{
qDebug() << "按钮被点击";
});
如果是非常简单的 Lambda,也可以使用三参数形式:
connect(button,
&QPushButton::clicked,
[]()
{
qDebug() << "按钮被点击";
});
不过,如果 Lambda 需要依赖某个 QObject 对象,实际项目中通常更适合提供 context:
connect(button,
&QPushButton::clicked,
this,
[this]()
{
ui->label->setText("Hello");
});
这样对象生命周期和 Lambda 的连接关系更加明确。
十三、不要死记 connect(),只需要问四个问题
以后看到:
connect(...)
不要先去背参数顺序。
先问:
① 谁发信号?
② 发的是什么信号?
③ 谁来处理?
④ 处理时执行什么函数?
例如:
点击按钮以后修改标签。
那么:
谁发信号?
↓
button
什么信号?
↓
clicked
谁处理?
↓
this
执行什么?
↓
handleClicked()
于是:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
自然就写出来了。
十四、总结
connect() 最重要的不是记住各种复杂重载,而是理解:
信号发生以后,调用什么函数。
最常见的成员函数写法:
connect(sender,
signal,
receiver,
function);
例如:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::handleClicked);
而连接 Lambda 或普通函数时,还可以使用三参数形式:
connect(sender,
signal,
functor);
例如:
connect(button,
&QPushButton::clicked,
[]()
{
qDebug() << "Hello";
});
因此,学习 connect() 时真正需要掌握的核心只有三层:
第一层:
理解“信号 → 函数”
第二层:
掌握成员函数和 Lambda 两种写法
第三层:
理解信号参数如何传递
等这三部分真正理解以后,再去学习 Qt::UniqueConnection、disconnect()、线程中的信号槽、重载信号等内容,会容易得多。
不要一开始就把 connect() 学成一堆语法。
先把这条最核心的关系搞懂:
对象发生事情
↓
发信号
↓
connect()
↓
执行对应函数
这才是 Qt 信号与槽最核心的思想。
2750



被折叠的 条评论
为什么被折叠?



