QT —— 信号和槽
我们之前对QT,有了一个大致的了解,如果还有对QT这个概念还不清楚的小伙伴可以点击这里:
今天我们来了解一下QT当中的信号和槽,这个主要就是和connect函数有关系:
信号和槽
其实我们之前已经简单使用过了信号和槽的功能,就是我们用其他方式实现HelloWorld的时候:
connect函数是最好的例子,这里的connect函数的意思是:mybutton会发出&QPushButton中的clicked信号,然后这个信号会被this(也就是Widget接收),接收之后,处理方法为handler:
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
mybutton = new QPushButton(this); //创建一个按钮
mybutton->move(200,300); //修改按钮位置
mybutton->setText("这是一个按钮");
connect(mybutton,&QPushButton::clicked,this,&Widget::handler);
}
void Widget::handler()
{
if(mybutton->text() == "这是一个按钮")
{
mybutton->setText("你点击了按钮");
}
else if(mybutton->text() == "你点击了按钮")
{
mybutton->setText("这是一个按钮");
}
}
Widget::~Widget()
{
delete ui;
}
其实可以把connect函数里面的参数可以分类一下:
这样的话我们就一目了然了,下面我们来介绍一下信号和槽的概念:
信号和槽(Signals & Slots)是Qt框架的核心特性,用于对象间的通信。它是一种强大的观察者模式实现,比传统的回调函数更灵活、更安全。
信号(Signal)
- 由对象在特定事件发生时发出的通知
- 只需声明,不需要实现(由moc自动生成)
- 可以带有参数,用于传递数据
槽(Slot)
- 响应特定信号的函数
- 需要实现,可以是普通成员函数
- 可以接收信号传递的参数
声明方式
class MyClass : public QObject
{
Q_OBJECT // 必须包含这个宏
public:
explicit MyClass(QObject *parent = nullptr);
signals: // 信号声明区
void valueChanged(int newValue);
void operationCompleted();
public slots: // 槽函数声明区
void setValue(int value);
void doSomething();
private slots:
void internalHandler();
};
工作原理
- 当信号被发射(emit)时,所有连接到该信号的槽函数都会被调用
- 一个信号可以连接多个槽
- 多个信号可以连接到一个槽
- 信号可以连接到另一个信号(形成信号链)
连接方式
1. 传统连接方式(Qt4风格)
connect(sender, SIGNAL(valueChanged(int)), receiver, SLOT(setValue(int)));
2. 新式连接方式(Qt5风格)
connect(sender, &SenderClass::valueChanged, receiver, &ReceiverClass::setValue);
区分槽函数和信号
我们在写代码的时候要注意槽函数和信号,槽函数的标识像一把梳子,而信号则有像WiFi一样的符号:
通过QtCreator生成信号槽代码
如果我们创建按钮是在ui界面上创建的,还有一种方式可以帮助我们隐式连接,我们进入ui,创建一个按钮,右击转到槽:
选择对应的信号:
点击OK,会自动生成一个函数:
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->pushButton->setText("这是一个按钮");
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
if(ui->pushButton->text() == "这是一个按钮")
{
ui->pushButton->setText("你点击了按钮");
}
else if(ui->pushButton->text() == "你点击了按钮")
{
ui->pushButton->setText("这是一个按钮");
}
}
这样和显示用connect是一样的效果:
自动生成槽函数
⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:
1、以"on"开头,中间使用下划线连接起来;
2、"XXX"表示的是对象名(控件的 objectName 属性)。
3、"SSS"表示的是对应的信号。
如:"on_pushButton_clicked()",pushButton代表的是对象名,clicked是对应的信号。
在Qt开发中,信号和槽的连接方式主要有两种风格:基于命名约定的自动连接和显式的connect
调用。虽然Qt支持通过特定命名规则自动连接信号槽,但我更推荐开发者使用显式connect
的方式,原因如下:
显式连接的优势
-
代码可读性更强
- 通过
connect
语句可以一目了然地看到对象间的通信关系 - 避免隐藏在命名规则中的"魔法连接",降低理解成本
- 通过
-
编译时检查(Qt5新语法)
- 使用新式语法
connect(sender, &Sender::signal, receiver, &Receiver::slot)
时 - 编译器会检查信号和槽的签名是否匹配,提前发现错误
- 使用新式语法
-
减少隐式错误
- 避免因拼写错误导致的连接失效
- 防止因重构改名而破坏原有连接
-
灵活性更高
- 可以方便地使用Lambda表达式
- 支持动态连接/断开
- 能够指定不同的连接类型(如跨线程的QueuedConnection)
命名约定自动连接的局限性
虽然Qt提供了on_对象名_信号名
这种命名约定来自动连接信号槽(通过QMetaObject::connectSlotsByName
),但这种方式的缺点包括:
-
隐式连接不够直观
- 连接关系分散在代码各处,难以追踪
- 新开发者可能不了解这种"魔法"行为
-
重构风险
- 修改UI对象名称时容易破坏现有连接
- 缺乏编译时检查,运行时才能发现错误
-
灵活性受限
- 难以实现复杂的连接逻辑
- 不支持Lambda表达式等现代C++特性
最佳实践建议
-
优先使用显式
connect
// 推荐 - Qt5新式语法 connect(ui->pushButton, &QPushButton::clicked, this, &MyWidget::handleButtonClick);
-
对于简单UI逻辑,可适度使用Lambda
connect(ui->pushButton, &QPushButton::clicked, [this](){ // 处理点击逻辑 });
-
避免完全依赖命名约定的自动连接
- 即使使用UI设计器生成的代码,也应检查确认连接关系
-
保持一致性
- 在项目中统一采用显式连接风格
- 在代码审查中检查连接方式
结论
虽然Qt框架提供了多种连接信号槽的方式,但显式使用connect
函数能够带来更好的代码可维护性和更少的运行时错误。特别是在大型项目或团队协作中,显式声明对象间的通信关系远比依赖隐式命名约定更为可靠。