Qt 信号与槽机制详解(中篇):自定义、重载与参数传递

1. 自定义信号与槽(基本语法)

在 Qt 中,不仅可以使用控件自带的信号,还可以为自己设计的类添加自定义信号和槽,用于对象间的通信。例如:老师类发出"上课了"的信号,学生类执行"回到座位"的槽。

1.1 自定义信号(书写规范)

  • 位置 :必须写在类的 signals: 区域下。

  • 返回值 :必须是 void

  • 实现只需声明,不需要定义 (由 moc 自动生成)。

  • 参数:可以有参数,也可以重载。

示例(在 teacher.h 中):

复制代码
class Teacher : public QObject
{
    Q_OBJECT
public:
    explicit Teacher(QObject *parent = nullptr);
signals:
    void classBegin();          // 无参信号
    void classBegin(QString msg); // 带参信号(重载)
};

1.2 自定义槽(书写规范)

  • 位置 :早期版本要求写在 public slots:protected slots:private slots: 下,高版本 Qt 也允许直接放在 public: 或全局

  • 返回值void

  • 实现需要声明,也需要定义(和普通函数一样)。

  • 参数:可以有参数,也可以重载。

示例(在 student.h 中):

复制代码
class Student : public QObject
{
    Q_OBJECT
public:
    explicit Student(QObject *parent = nullptr);
public slots:   // 或直接 public:
    void goToClass();           // 无参槽
    void goToClass(QString lesson); // 带参槽
};

student.cpp 中实现槽:

复制代码
void Student::goToClass()
{
    qDebug() << "回到座位,开始学习!";
}
void Student::goToClass(QString lesson)
{
    qDebug() << "开始上" << lesson << "课!";
}

1.3 发射信号:emit 关键字

信号需要被"发射"才能触发槽。使用 emit 关键字(实际上是一个空宏,仅用于提示)。

复制代码
emit teacher->classBegin();      // 发射无参信号
emit teacher->classBegin("数学"); // 发射带参信号

注意:先 connectemit,否则信号无人接收。

2. 完整示例:老师上课,学生响应

我们通过一个经典案例来串联上述知识:

场景:老师对象发出"上课了"的信号,学生对象执行"回到座位,开始学习"的槽。

2.1 新建老师类(Teacher)

  • 继承自 QObject(便于使用信号槽和对象树)。

  • signals: 中声明 classBegin()

2.2 新建学生类(Student)

  • 继承自 QObject

  • public slots: 中声明 goToClass(),并在 .cpp 中实现。

2.3 在 Widget 中关联

widget.h 中声明成员变量:


3. 带参数的信号与槽(重载与匹配)

3.1 为什么要带参数?

信号可以携带数据,比如传递按钮状态、输入框内容、当前进度等。槽函数接收这些参数并处理。

核心规则 :信号函数的参数列表必须和对应连接的槽函数参数列表一致(或信号参数多于槽,但一般不建议)。

3.2 重载信号槽

信号和槽都可以重载(多个同名但参数不同的版本)。关联重载函数时,需要用函数指针明确指定版本,否则编译器无法确定。

示例:在 Widget 中声明重载信号和槽:

复制代码
signals:
    void MySignal();          // 无参
    void MySignal(int value); // 带 int

public slots:
    void MySlot();
    void MySlot(int value);

.cpp 中连接:

复制代码
// 使用函数指针明确选择无参版本
void (Widget::*sigVoid)() = &Widget::MySignal;
void (Widget::*slotVoid)() = &Widget::MySlot;
connect(this, sigVoid, this, slotVoid);

// 选择带参版本
void (Widget::*sigInt)(int) = &Widget::MySignal;
void (Widget::*slotInt)(int) = &Widget::MySlot;
connect(this, sigInt, this, slotInt);

注意 :函数指针要指明作用域(Widget::*),否则编译报错。

3.3 参数个数匹配规则

  • 信号参数个数可以多于槽参数个数(多的参数会被忽略)。

  • 槽参数个数不能多于信号参数个数(否则编译失败)。

  • 类型必须兼容(例如 QString 不能传给 int)。

但为了清晰和安全,强烈建议保持参数个数和类型完全一致


4. 信号与槽的连接关系(多对多)

4.1 一对一(最常用)

一个信号对应一个槽(之前所有示例都是)。

4.2 一对多(一个信号连接多个槽)

同一个信号可以连接多个不同的槽,信号发射后,所有关联的槽会按连接顺序依次执行

示例

复制代码
connect(this, &Widget::MySignal, this, &Widget::slot1);
connect(this, &Widget::MySignal, this, &Widget::slot2);
connect(this, &Widget::MySignal, this, &Widget::slot3);
// 发射信号后,三个槽依次执行
emit MySignal();

4.3 多对一(多个信号连接同一个槽)

多个不同的信号可以连接到同一个槽函数,槽函数会被多次调用。

示例

复制代码
connect(btn1, &QPushButton::clicked, this, &Widget::commonSlot);
connect(btn2, &QPushButton::clicked, this, &Widget::commonSlot);
connect(this, &Widget::signalA, this, &Widget::commonSlot);

4.4 信号连接信号

一个信号可以连接到另一个信号,形成信号链。当第一个信号发射时,会触发第二个信号,进而触发其关联的槽。

用途:在某些场景下,你想在中间做一些额外处理,或者只是转发信号。

示例

复制代码
connect(btn, &QPushButton::clicked, this, &Widget::MySignal); // 按钮点击 → 发射 MySignal
connect(this, &Widget::MySignal, this, &Widget::MySlot);      // MySignal → 执行槽

此时点击按钮,MySignal 被发射,随后 MySlot 执行。

5. 断开连接:disconnect

如果不再需要某个连接,可以使用 QObject::disconnect() 断开。

复制代码
disconnect(btn, &QPushButton::clicked, this, &Widget::close);

通常不主动断开,因为对象销毁时会自动断开所有连接,但某些特殊场景(如临时禁用响应)会用到。