一、信号与槽是什么?
-
基本概念
信号(Signal)
:对象在特定事件发生时发出的通知(如按钮被点击、数据更新)。
-
声明在类的
signals:
区块,无需实现。 -
示例:
void clicked();
槽(Slot)
:接收信号并执行具体操作的函数(如更新界面、处理数据)。
- 声明为普通成员函数或
slots:
区块,必须实现。 - 示例:
void updateUI();
-
-
核心特点
- 松耦合:按钮不需要知道谁处理点击事件,只需发出信号。
- 类型安全:Qt5后支持编译时检查参数类型和数量。
- 跨线程通信:通过
Qt::QueuedConnection
安全传递事件。
二、如何声明和使用?
-
声明信号与槽
cpp
// 自定义按钮类(发送信号) class MyButton : public QPushButton {Q_OBJECT // 必须添加 signals:void doubleClicked(); // 声明信号 };// 窗口类(接收信号) class MainWindow : public QWidget {Q_OBJECT public slots: // 槽函数void onDoubleClick() { qDebug() << "按钮被双击!";} };
-
三种连接方式
Qt5推荐写法
(类型安全):
cpp
connect(按钮对象指针, &MyButton::doubleClicked, 窗口对象指针, &MainWindow::onDoubleClick);
Lambda表达式
(简化简单逻辑):
cpp
connect(btn, &QPushButton::clicked, [=]() {qDebug() << "按钮被点击,当前时间:" << QTime::currentTime(); });
Qt4旧语法
(不推荐,易出错):
cpp
connect(btn, SIGNAL(clicked()), this, SLOT(handleClick()));
-
触发信号
使用emit
关键字触发自定义信号:cpp
// 在MyButton的某个方法中 if (双击事件) {emit doubleClicked(); // 发出信号 }
三、参数传递规则
-
参数匹配
信号和槽的参数需按顺序一致,且槽的参数数量可少于信号的:cpp
// 信号:两个参数 void dataSent(QString id, int value);// 槽:可只接收部分参数 void logData(QString id) { /* 仅使用id */ } connect(sensor, &Sensor::dataSent, logger, &Logger::logData);
-
传递自定义类型
若参数为自定义结构体或类,需注册元类型:cpp
qRegisterMetaType<MyData>("MyData"); // 注册 emit sendData(MyData(...)); // 使用
四、高级用法与技巧
-
信号连接信号
实现事件链式触发:cpp
connect(btnMinimize, &QPushButton::clicked, window, &QWindow::showMinimized); connect(window, &QWindow::windowStateChanged, this, &MainWindow::onWindowStateChanged);
-
多线程安全连接
跨线程时使用队列连接,避免直接访问UI:cpp
connect(workerThread, &Worker::resultReady, this, &MainUI::updateResult, Qt::QueuedConnection);
-
自动断开连接
使用QMetaObject::Connection
管理连接,避免内存泄漏:cpp
QMetaObject::Connection conn = connect(...); disconnect(conn); // 需要时手动断开
五、常见问题与调试
-
为什么槽函数不执行?
- 检查对象是否被销毁(如局部对象超出作用域)。
- 确认连接代码是否执行(如未进入构造函数)。
- 使用
qDebug() << connect(...);
输出连接是否成功。
-
参数类型不匹配
-
Qt5语法会在编译时报错,如:
cpp
connect(btn, &QPushButton::clicked, this, &MainWindow::handleClick(int)); // 错误:信号无参数
-
-
Lambda捕获陷阱
-
避免在Lambda中捕获可能失效的指针:
cpp
connect(btn, &QPushButton::clicked, [this] { // 正确:捕获当前对象的this指针this->doSomething(); });
-
六、实战示例:登录界面
cpp
// 登录按钮点击处理
connect(ui->btnLogin, &QPushButton::clicked, this, [this] {QString username = ui->editUser->text();QString password = ui->editPass->text();if (validateUser(username, password)) {emit loginSuccess(username); // 发出成功信号} else {showError("用户名或密码错误!");}
});// 登录成功跳转页面
connect(this, &LoginWindow::loginSuccess, mainWindow, &MainWindow::showWelcomePage);
七、总结
- 核心要点
- 所有类必须继承
QObject
并添加Q_OBJECT
宏。 - 优先使用Qt5的函数指针语法,避免运行时错误。
- 跨线程通信选择
Qt::QueuedConnection
。
- 所有类必须继承