奇牛Qt02-03 信号槽 —— 奇牛+gemini

奇牛Qt02-03 信号槽 —— 奇牛+gemini

1. 信号和槽机制概述

1.1 什么是信号和槽?

信号和槽是 Qt 的核心机制,用于实现对象之间的通信。当某个对象的状态发生改变、想通知另一个对象做某件事时,该对象就会发送一个信号(Signal),另一个对象收到信号后,就会执行之前约定好的特定函数,即槽(Slot)

信号和槽机制的核心要素包括:

  1. 信号发送者(Sender)
  2. 信号接收者(Receiver)
  3. 信号:由发送者触发的特殊函数,不需要程序员实现函数体
  4. :由接收者执行的响应函数。

在使用信号槽之前,必须先做好它们之间的关联,并在需要的时候由发送者通过代码或用户操作触发信号。

2. 信号槽 vs C/C++ 回调函数

  • 松耦合:信号发送者不需要了解接收者的具体接口信息;而回调函数属于紧密耦合,必须直接调用目标对象的特定函数。
  • 灵活性:信号槽相比回调函数使用起来更灵活,支持一对多、多对一的连接关系。
  • 缺点(性能损耗):信号槽的执行速度比回调函数慢。因为回调函数全是同步执行的,而信号槽在跨线程等特殊连接方式下可能是异步的(仅在直连方式下才是同步执行)。

2. 信号和槽的关联方式

2.1 使用 connect 函数手动

2.1.1 写法一:基于 SIGNALSLOT 宏(旧版本/传统写法)

该方式使用宏将信号和槽函数转换成 const char* 字符串。

connect(ui->scrollBar, SIGNAL(valueChanged(int)), ui->label, SLOT(setNum(int)));
  • 注意点
    • 信号和槽有参数时,必须带上参数类型,但不能带变量名(如写 int,不能写 int value)。
    • 函数不需要、也不能使用类名限定符(直接写函数名即可)。

2.1.2 写法二:基于函数指针(Qt 5 推荐写法)

直接传递信号和槽函数的地址。

connect(ui->lineEdit, &QLineEdit::textChanged, ui->label, &QLabel::setText);
  • 注意点
    • 信号和槽必须使用类名进行作用域限定(如 &QLineEdit::textChanged)。
    • 要求信号和槽的参数个数相同(或信号的更多),且能进行合适的隐式类型转换,否则编译失败。
    • 二义性陷阱:如果槽函数存在重载(例如 QLabel::setNum(int)QLabel::setNum(double) 并存),使用函数指针编译器会因为不知道关联哪个而报错。

2.2 UI 设计器自动连接(转到槽)

在 Qt Creator 的 UI 设计器中,可以通过可视化的方式快速生成槽函数:

  1. 在控件上右键单击,选择 “转到槽...” (Go to slot...)
  2. 在弹出的窗口中选择对应的信号(如 textChanged(QString)),点击 OK。

此时 Qt Creator 会在类中自动生成符合特定命名规则的空槽函数:

  • 命名规则void on_接收者对象名_信号名(参数列表);
  • 内部原理:Qt 框架会在运行时通过内省机制,根据槽函数的命名自动识别发送者和信号并完成关联。

C++

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots: // 自动连接或手写规则槽函数时,slots 宏必不可少!
    void on_lineEdit_textChanged(const QString &arg1);  // 设计器自动生成
    void on_lineEdit2_textChanged(const QString &arg); // 按规则手动添加
};
  • 提示:通过 connect 手动关联的槽函数,在现代 Qt 5 中不需要放在 private slots: 下,放在普通的 publicprivate 下作为普通函数即可。

3. 自定义信号与槽

3.1 自定义信号

自定义信号必须声明在 signals: 访问权限下。

  • 信号只是一个空壳函数,不需要、也不能有对应的函数体实现
  • 信号的参数个数可以大于槽函数的参数个数,但绝对不能少于槽函数的参数个数。
  • 构建项目时,元对象编译器(moc)扫描到 signals 会在 moc_*.cpp 中自动生成其底层实现代码。
class ReceivePackThread : public QThread {
    Q_OBJECT
public:
    ReceivePackThread(QSerialPort *port);

signals:
    // 自定义信号,不需要在.cpp中写实现
    void receiveResponsePack(char cmd, char param, int data);
};

3.2 发送信号

  • 自动发送:Qt 标准组件在用户交互时会自动触发对应信号(如点击按钮触发 clicked())。
  • 代码手动发送:在需要触发信号的地方,使用 emit 关键字激发信号(非必须,但写上方便阅读)。
void MainWindow::on_pushButton_clicked() {
    Student s;
    s.age = 18;
    s.score = 100;
    
    // 手动触发自定义信号,并将参数传递出去
    emit mySignal(s);
}

4. 信号和槽的连接方式(第 5 个参数)

在使用 connect 关联信号槽时,可以指定第 5 个参数 Qt::ConnectionType,它决定了信号和槽的同步/异步行为(默认值为 Qt::AutoConnection):

连接类型 通俗解释 核心特点与适用场景
Qt::DirectConnection (直接连接) 面谈 / 打电话 同步执行。信号一发出,槽函数立刻在发送者所在的线程执行。执行完了,后面的代码才会继续走。
Qt::QueuedConnection (队列连接) 发微信留言 异步执行。信号发出后变成一个任务,丢进接收者线程的任务队列排队。发完信号代码直接往下走,不管槽函数执行完没有。多线程通信最常用
Qt::BlockingQueuedConnection (阻塞队列连接) 急件派送,原地等待 强行阻塞。信号丢给接收者线程去排队执行,但发送者线程会原地死等,直到接收者把槽函数执行完了,发送者才继续动。绝对不能在同一个线程用,否则自己等自己,直接卡死(死锁)
Qt::AutoConnection (自动连接) 智能判断 (默认值) Qt 自动选择:如果发送者和接收者在同一个线程,自动变更为直接连接;如果在不同线程,自动变更为队列连接
Qt::UniqueConnection (唯一连接) 防重加锁 不能单独用,需要用 `

UniqueConnection示例:

// 第一次调用,连接成功
auto ret = connect(ui->lineEdit, &QLineEdit::textChanged, ui->label, &QLabel::setText, 
                   Qt::ConnectionType(Qt::AutoConnection | Qt::UniqueConnection));
qDebug() << ret; // 输出 true

// 第二次调用,由于 UniqueConnection 限制,连接会失败,避免了重复触发
ret = connect(ui->lineEdit, &QLineEdit::textChanged, ui->label, &QLabel::setText, 
              Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));
qDebug() << ret; // 输出 false

5. 信号和槽的对应与断开

5.1 对应关系

信号与槽支持灵活的多对多映射关系:

  • 一个信号关联多个槽:当信号发出时,槽函数按照连接建立的先后顺序依次被调用。
  • 多个信号关联同一个槽

5.2 信号槽的断开 (disconnect)

可以使用 QObject::disconnect 显式断开某个信号槽的连接。

// 动态断开指定的信号槽连接
disconnect(btn, &QPushButton::clicked, this, &MainWindow::myTest);

补充:disconnect 的通配符(使用 0 代替)高级用法:

  1. 断开该对象的所有信号连接

    disconnect(myObject, 0, 0, 0); 
    // 等效于非静态成员函数:myObject->disconnect();
    
  2. 断开与特定信号的所有连接

    disconnect(myObject, SIGNAL(mySignal()), 0, 0);
    // 等效于:myObject->disconnect(SIGNAL(mySignal()));
    
  3. 断开与特定接收者的所有连接

    disconnect(myObject, 0, myReceiver, 0);
    // 等效于:myObject->disconnect(myReceiver);
    

6. 信号槽传递自定义类型的参数

6.1 核心问题

在自定义信号中传递自定义数据类型(如 classstruct)时,如果在同一个线程内(同步直连),可以直接安全地使用。但如果是跨线程使用信号槽(队列连接),由于涉及到参数生命周期的拷贝与队列排队,直接使用自定义类型可能会导致编译运行错误

6.2 解决方案:qRegisterMetaType

必须通过 qRegisterMetaType 将自定义数据类型注册到 Qt 的元对象系统中。

  • 注册时机:在使用该信号槽之前(通常在构造函数中)进行注册。
  • 基本语法qRegisterMetaType<MyClass>("MyClass");
  • 注意:如果信号槽参数里包含引用类型,引用类型需要单独注册,如 qRegisterMetaType<MyClass>("MyClass&");
posted @ 2026-07-06 22:12  学习笔记草稿存放账号  阅读(5)  评论(0)    收藏  举报