Qt 的 connect
函数有三种常见的用法:1.使用 SIGNAL
和 SLOT
宏、2.使用 &类名::函数名
、以及3.使用 Lambda 表达式。每种方法各有优缺点和注意事项。
1. 使用 SIGNAL
和 SLOT
宏
connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(onPushButtonClicked()));
参数解析:
sender
:信号的发送者对象的指针,这里是ui->pushButton
。SIGNAL(clicked())
:信号的成员函数的字符串形式,其中clicked
是信号函数的名称。receiver
:信号的接收者对象的指针,这里是this
(当前对象)。SLOT(onPushButtonClicked())
:槽函数的字符串形式,其中onPushButtonClicked
是槽函数的名称。
优点:
- 适用于 Qt 4 及更早版本。
- 直观,容易理解和使用。
缺点:
- 依赖字符串匹配,容易出错,编译时不会检查函数签名,只有在运行时出错。 其中
SIGNAL()
和SLOT()
是宏,使用宏将信号和槽函数转换为字符串,编译器无法对字符串进行类型检查,因此在编译时无法发现连接错误。如果信号或槽的名称拼写错误或者类型不匹配,只有在运行时才会报错,不利于代码调试和维护,因此不推荐使用。 - 难以进行代码重构(如重命名信号或槽时需要手动更新字符串)。
- 缺乏类型安全性。
注意事项:
- 确保信号和槽的字符串匹配正确。
- 使用前需要在头文件中声明槽函数。
2. 使用 &类名::函数名
connect(ui->pushButton_2, &QPushButton::clicked, this, &Widget::onSetBlockedSignalStatus);
参数解析:
sender
:信号的发送者对象的指针,这里是ui->pushButton_2
。&QPushButton::clicked
:信号的成员函数指针,其中QPushButton
是发送者对象的类名,clicked
是信号函数的名称。receiver
:信号的接收者对象的指针,这里是this
(当前对象)。&Widget::onSetBlockedSignalStatus
:槽函数的成员函数指针,其中Widget
是接收者对象的类名,onSetBlockedSignalStatus
是槽函数的名称。
优点:
- 适用于 Qt 5 及更高版本。
- 类型安全,编译时会检查信号和槽的匹配性。编译器可以在编译时检查到连接错误。如果信号或槽函数的名称拼写错误或者类型不匹配,编译器会报错,而不是等到运行时才发现错误。
- 支持重载函数:支持连接重载函数的信号与槽,编译器会根据函数参数的类型自动匹配合适的重载函数。
- 支持 Lambda 表达式:你也可以在
connect()
中使用 Lambda 表达式作为槽函数,使得连接更加灵活。 - 更易于代码重构和维护。
缺点:
- 需要更详细的语法,可能对初学者不太友好。
注意事项:
- 确保信号和槽函数的签名匹配。
- 槽函数必须在头文件中声明。
3. 使用 Lambda 表达式
connect(ui->pushButton, &QPushButton::clicked, [=]() {
ui->label->setText("Hello Qt");
});
参数解析:
sender
:信号的发送者对象的指针,这里是ui->pushButton
。&QPushButton::clicked
:信号的成员函数指针,其中QPushButton
是发送者对象的类名,clicked
是信号函数的名称。receiver
:由于使用 Lambda 表达式,这里没有明确的接收者对象指针。- Lambda 表达式:信号触发时要执行的代码块,直接在
connect
语句中定义。[=]
是捕获列表,用于捕获外部变量。
优点:
- 适用于 Qt 5 及更高版本。
- 灵活,不需要在类中声明槽函数。
- 可以直接在
connect
语句中定义要执行的代码,简化代码逻辑。
缺点:
- Lambda 表达式中的代码不易复用。
- 过多使用可能导致代码难以阅读和维护。
注意事项:
- Lambda 表达式中的捕获列表需要正确捕获外部变量(如
[=]
、[&]
或具体变量名)。 - Lambda 表达式中的代码应保持简洁。
-
Lambda表达式是C++ 11 的内容,所以,需要再Pro项目文件中加入 CONFIG += C++ 11
4.ui界面--转到槽--功能
补充:在 Qt Designer 中也可以通过图形界面将信号与槽连接起来。也就是我们在ui界面常用的“”转到槽“功能。具体实现原理笔者也不太清楚,之前看到过一篇文章说也是实现类似信号与槽连接的,这里想到了就提一下。
5.例子,展示了三种 connect
用法:
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = nullptr) : QWidget(parent) {
QVBoxLayout *layout = new QVBoxLayout(this);
QPushButton *button1 = new QPushButton("Button 1", this);
QPushButton *button2 = new QPushButton("Button 2", this);
QPushButton *button3 = new QPushButton("Button 3", this);
QLabel *label = new QLabel("Label", this);
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(label);
// 使用 SIGNAL 和 SLOT 宏
connect(button1, SIGNAL(clicked()), this, SLOT(onButton1Clicked()));
// 使用 &类名::函数名
connect(button2, &QPushButton::clicked, this, &Widget::onButton2Clicked);
// 使用 Lambda 表达式
connect(button3, &QPushButton::clicked, [=]() {
label->setText("Hello Qt");
});
}
public slots:
void onButton1Clicked() {
qDebug() << "Button 1 clicked!";
}
void onButton2Clicked() {
qDebug() << "Button 2 clicked!";
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
#include "main.moc"
5.总结
- 使用
SIGNAL
和SLOT
宏:适用于旧版本的 Qt,容易理解但缺乏类型安全性,容易出错。 - 使用
&类名::函数名
:类型安全,适用于现代 Qt 版本,适合复杂项目和长期维护。 - 使用 Lambda 表达式:灵活简洁,适合简短的处理逻辑,不需要在类中声明槽函数,但不易复用。
选择哪种方法取决于具体的需求和项目的复杂性。对于简单的操作,Lambda 表达式非常方便;而对于大型项目,推荐使用类型安全的 &类名::函数名
方式。