Qt信号与槽机制深度解析

1. 什么是信号槽机制

信号槽是Qt框架的核心特性之一,它提供了一种对象间通信的机制。与传统的回调函数相比,信号槽机制具有以下优势:

  • 类型安全:信号和槽的参数类型由编译器检查
  • 松耦合:发送者不知道也不关心接收者是谁
  • 灵活性:一个信号可以连接多个槽,多个信号也可以连接同一个槽

在Qt中,信号(Signal)是在特定事件发生时被发射的事件通知 ,而槽(Slot)是对信号做出响应的成员函数。当信号被发射时,所有连接到该信号的槽函数都会被自动调用。

2. 信号和槽的关联

信号和槽通过QObject::connect()函数建立连接,基本语法如下:

cpp 复制代码
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));

在示例代码中,我们看到了两种连接方式:

cpp 复制代码
// 方式1:新式语法(推荐)
connect(btn, &QPushButton::clicked, this, &MainWindow::mySlot);

// 方式2:旧式语法
connect(btn, SIGNAL(clicked()), this, SLOT(mySlot()));

关键点

  • 使用SIGNAL()SLOT()宏时,函数原型只能包含参数类型,不能有参数名
  • 新式语法在编译时进行类型检查,更安全
  • 连接成功后,当信号被发射时,槽函数会自动调用

3. 定义自己的信号

在Qt中,我们可以自定义信号。信号需要在类的signals:区域声明,且不需要实现

cpp 复制代码
signals:
    void mySignal(Student stu);

信号特点

  • 返回类型必须是void
  • 信号可以有参数,参数类型必须能被Qt的元对象系统识别
  • 信号只需声明,不需实现
  • 使用emit关键字发射信号

4. 定义自己的槽

槽是普通的成员函数,可以在public slots:protected slots:private slots:区域声明:

cpp 复制代码
public slots:
    void mySlot();
    void mySlot2(Student stu);

槽的特点

  • 可以是任何访问权限的成员函数
  • 可以有返回值,但通常返回void
  • 可以有参数,参数类型必须与连接的信号兼容
  • 需要实现函数体

5. 发送信号

使用emit关键字发送信号:

cpp 复制代码
void MainWindow::mySlot()
{
    Student s;
    s.age = 19;
    s.score = 100;
    
    emit mySignal(s);  // 发射信号
}

6. 信号和槽的连接方式

Qt提供了多种连接类型,通过Qt::ConnectionType指定:

连接类型 描述
Qt::AutoConnection 默认方式,自动决定连接类型
Qt::DirectConnection 信号发射时立即调用槽,在发射线程执行
Qt::QueuedConnection 槽在接收者线程的事件循环中调用
Qt::BlockingQueuedConnection 类似QueuedConnection,但发送线程会阻塞
Qt::UniqueConnection 防止重复连接相同的信号和槽

7. 信号和槽的对应关系

信号和槽的连接可以有以下几种形式:

  1. 一对一:一个信号连接一个槽
  2. 一对多:一个信号连接多个槽(按连接顺序执行)
  3. 多对一:多个信号连接同一个槽
  4. 信号连接信号:一个信号触发另一个信号

示例代码中展示了多种连接方式:

cpp 复制代码
// 一个信号连接一个槽
connect(btn, SIGNAL(clicked()), this, SLOT(mySlot()));

// 信号连接信号
connect(this, SIGNAL(mySignal(Student)), this, SLOT(mySlot2(Student)));

8. 信号槽的断开

使用disconnect()函数断开信号槽连接:

cpp 复制代码
// 断开特定信号和槽
disconnect(btn, SIGNAL(clicked()), this, SLOT(mySlot()));

// 断开对象所有连接
disconnect(btn, 0, 0, 0);
btn->disconnect();

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

要传递自定义类型,必须使用qRegisterMetaType()注册:

cpp 复制代码
// 注册自定义类型
qRegisterMetaType<Student>("Student");

// 然后才能用于信号槽连接
connect(this, SIGNAL(mySignal(Student)), this, SLOT(mySlot2(Student)));

要求

  • 自定义类型必须提供公有的默认构造函数、拷贝构造函数和析构函数
  • 对于非QObject派生类,还需要使用Q_DECLARE_METATYPE宏声明

10. 完整代码回顾

mainwindow.h

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QPushButton>
#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

struct Student {
    int age;
    int score;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

public slots:
    void mySlot();
    void mySlot2(Student stu);

signals:
    void mySignal(Student stu);

private:
    Ui::MainWindow *ui;
    QPushButton *btn;
};
#endif // MAINWINDOW_H

mainwindow.cpp

cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 创建一个按钮
    btn = new QPushButton(this);

    // 设置按钮位置
    btn -> setGeometry(100,50,  // x,y坐标,原点是左上角
                       80,40);  // 宽度 高度

    btn -> setText("我的按钮");

    // 把自定义的数据类型Student, 注册到元数据
    qRegisterMetaType<Student>("Student");

    //connect(btn,&QPushButton::clicked,this,&MainWindow::mySlot);

    connect(btn, SIGNAL(clicked()),
            this, SLOT(mySlot()));

    connect(this, SIGNAL(mySignal(Student)),
            this, SLOT(mySlot2(Student)));

//    connect(btn, SIGNAL(clicked()),
//            this, SLOT(mySlot()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::mySlot()
{
//    qDebug() << "已经点击";
//    static int count = 0;
//    count++;
//    if(count >= 4){
//        disconnect(btn, SIGNAL(clicked()),
//                   this, SLOT(mySlot()));
        //disconnect(btn, 0, 0, 0);
        //}
    // 传递自定义类类型Student
    Student s;
    s.age = 19;
    s.score = 100;

    emit mySignal(s);
}

void MainWindow::mySlot2(Student stu)
{
    qDebug() << stu.age << stu.score;
}

main.cpp

cpp 复制代码
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00614 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript