简单易懂的计数器(理解Qt的信号和槽机制)

1.项目结构

2.核心代码

counterApp.pro (Qt项目文件)

bash 复制代码
QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    counter.cpp \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    counter.h \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

counter.h(定义类和信号槽)

cpp 复制代码
#ifndef COUNTER_H
#define COUNTER_H

#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>

class Counter : public QWidget
{
    Q_OBJECT  // 必须的宏,启用Qt元对象系统(信号槽所需)

public:
    explicit Counter(QWidget *parent = nullptr);

signals:  // 信号声明区域
    // 信号只有声明,没有实现,由Qt的moc自动生成
    void counterChanged(int newValue);  // 自定义信号:计数器值变化时发射
    void counterReachedTen();           // 自定义信号:计数器达到10时发射

public slots:  // 槽函数声明区域
    void increaseCounter();             // 槽函数:增加计数器
    void decreaseCounter();             // 槽函数:减少计数器
    void resetCounter();                // 槽函数:重置计数器

    void onCounterChanged(int value);   // 槽函数:响应counterChanged信号
    void onCounterReachedTen();         // 槽函数:响应counterReachedTen信号

private:
    int count = 0;
    QLabel *countLabel;
    QPushButton *incButton;
    QPushButton *decButton;
    QPushButton *resetButton;
    QLabel *statusLabel;
};

#endif // COUNTER_H

counter.cpp(实现类)

cpp 复制代码
#include "counter.h"

Counter::Counter(QWidget *parent) : QWidget(parent)
{
    // 初始化UI组件
    countLabel = new QLabel("当前计数: 0", this);
    countLabel->setAlignment(Qt::AlignCenter);

    statusLabel = new QLabel("状态: 正常", this);
    statusLabel->setAlignment(Qt::AlignCenter);

    incButton = new QPushButton("增加 (+1)", this);
    decButton = new QPushButton("减少 (-1)", this);
    resetButton = new QPushButton("重置 (0)", this);

    // 布局
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(countLabel);
    layout->addWidget(statusLabel);
    layout->addWidget(incButton);
    layout->addWidget(decButton);
    layout->addWidget(resetButton);

    setLayout(layout);
    setWindowTitle("Qt信号槽示例 - 计数器");

    // ================== 信号槽连接示例 ==================

    // 示例1: 按钮点击信号 -> 槽函数
    // clicked()是QPushButton的内置信号
    connect(incButton, &QPushButton::clicked, this, &Counter::increaseCounter);
    connect(decButton, &QPushButton::clicked, this, &Counter::decreaseCounter);
    connect(resetButton, &QPushButton::clicked, this, &Counter::resetCounter);

    // 示例2: 自定义信号 -> 槽函数
    // 当计数器变化时,发射counterChanged信号,触发onCounterChanged槽
    connect(this, &Counter::counterChanged, this, &Counter::onCounterChanged);

    // 示例3: 自定义信号 -> 槽函数(带条件触发)
    connect(this, &Counter::counterReachedTen, this, &Counter::onCounterReachedTen);

    // 示例4: 一个信号连接多个槽
    // 当计数器变化时,同时更新两个标签
    connect(this, &Counter::counterChanged, this, [this](int value) {
        countLabel->setText(QString("当前计数: %1").arg(value));
    });

    // 示例5: 按钮点击直接触发匿名函数(lambda表达式作为槽)
    connect(incButton, &QPushButton::clicked, this, [this]() {
        statusLabel->setText("状态: 上次操作是增加");
    });

    // 示例6: 断开连接示例(当计数为5时断开减少按钮)
    connect(this, &Counter::counterChanged, this, [this](int value) {
        if (value == 5) {
            disconnect(decButton, &QPushButton::clicked, this, &Counter::decreaseCounter);
            decButton->setText("减少 (-1) [已禁用]");
            decButton->setEnabled(false);
        }
    });
}

// 槽函数实现:增加计数器
void Counter::increaseCounter()
{
    count++;
    qDebug() << "计数器增加,当前值:" << count;

    // 发射自定义信号
    emit counterChanged(count);

    // 条件发射信号:当计数达到10时
    if (count == 10) {
        emit counterReachedTen();
    }
}

// 槽函数实现:减少计数器
void Counter::decreaseCounter()
{
    count--;
    qDebug() << "计数器减少,当前值:" << count;
    emit counterChanged(count);
}

// 槽函数实现:重置计数器
void Counter::resetCounter()
{
    count = 0;
    qDebug() << "计数器重置为0";
    emit counterChanged(count);
}

// 槽函数实现:响应counterChanged信号
void Counter::onCounterChanged(int value)
{
    qDebug() << "收到counterChanged信号,新值:" << value;

    // 根据计数值改变颜色
    if (value > 0) {
        countLabel->setStyleSheet("color: green; font-size: 16px;");
    } else if (value < 0) {
        countLabel->setStyleSheet("color: red; font-size: 16px;");
    } else {
        countLabel->setStyleSheet("color: black; font-size: 16px;");
    }
}

// 槽函数实现:响应counterReachedTen信号
void Counter::onCounterReachedTen()
{
    QMessageBox::information(this, "恭喜", "计数器达到10啦!🎉");
    statusLabel->setText("状态: 已到达10");
}

main.cpp(程序入口)

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

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // MainWindow w;
    // w.show();
    Counter counter;
    counter.resize(300, 200);
    counter.show();
    return a.exec();
}

3.运行效果

4.核心概念解释

信号 (Signal)

  • 信号是事件发生时发射的通知

  • 声明在类的 signals:区域

  • 只有声明,没有实现

  • 使用 emit signalName(parameters);发射信号

  • 特点

  • 不关心谁接收

  • 可以带参数传递数据

  • 由moc自动生成实现代码

cpp 复制代码
// 声明
signals:
    void valueChanged(int newValue);

// 发射
emit valueChanged(5);

槽 (Slot)

  • 槽是响应信号的函数

  • 声明在 public slots:private slots:区域

  • 特点

  • 需要完整实现

  • 可以是普通成员函数、lambda表达式

  • 参数必须与连接的信号匹配

cpp 复制代码
public slots:
    void onValueChanged(int value);  // 槽声明

连接 (Connect)

  • 作用:建立信号和槽的映射关系

  • 时机:在程序初始化时建立(通常是构造函数)

  • 语法connect(发送者, 信号, 接收者, 槽)

  • 必须性必须先connect,信号发射才有效

cpp 复制代码
connect(sender, &SenderClass::signalName,
        receiver, &ReceiverClass::slotName);

5.混淆点

信号发射后才建立连接。X

正确的理解是

  1. 连接(connect)是注册/绑定操作,在程序初始化时(通常是构造函数中)完成

  2. 信号发射(emit)是触发操作,在用户交互或程序逻辑中发生

  3. 必须先有连接,然后发射信号才有效

  4. 如果没有连接,发射信号相当于空操作

简单说

  • connect​ = 插上电源线

  • emit​ = 按下开关

  • 槽函数执行​ = 灯泡亮起

必须先插上电源线,按下开关灯泡才会亮!

6.核心工作流程

html 复制代码
程序启动
    ↓
构造函数执行
    ↓
创建UI组件
    ↓
建立所有connect连接 ← 关键!连接在这里建立!
    ↓
等待用户操作
    ↓
用户触发事件 → 发射信号 → 触发已连接的槽函数
    ↓
槽函数执行处理逻辑

7.关键理解点

1. 连接是注册,不是触发

  • connect()只是告诉Qt:"当A发射信号X时,请调用B的槽Y"

  • 连接操作是立即执行的注册操作

  • 连接建立后,映射关系一直存在直到断开

2. 信号发射是广播

  • emit signal()相当于广播:"事件发生了!"

  • 如果有连接,Qt会自动调用所有连接的槽函数

  • 如果没有连接,什么都不会发生

3. 槽是被动调用

  • 槽函数不会主动执行

  • 只有对应的信号发射时,Qt才会调用它

  • 槽函数是回调函数

8. moc(元对象编译器)的作用

  1. 扩展C++:让C++支持信号槽、反射等特性

  2. 生成代码:为信号生成实现代码

  3. 元对象系统:创建类的元信息(类名、方法、属性等)

  4. 运行时支持:支持动态类型检查、属性系统

html 复制代码
源代码(.h) → moc → 预处理代码(.cpp) → 编译器
                ↓
          添加"魔法代码"(信号实现、元对象等)

9.总结

当使用信号槽时,请确保:

  1. ✅ 类必须直接或间接继承自QObject

    html 复制代码
    // Qt 官方继承链
    QObject
       ↳ QWidget
           ↳ QPushButton, QLabel, QLineEdit...
           ↳ Counter类
  2. ✅ 类声明中有 Q_OBJECT

  3. ✅ 信号声明在 signals:区域

  4. ✅ 槽声明在 slots:区域

  5. ✅ 在构造函数中 调用 connect()建立连接

  6. ✅ 在事件处理中 使用 emit发射信号

  7. ✅ 信号和槽的参数类型匹配

  8. ✅ 使用新语法:connect(发送者, &类名::信号, 接收者, &类名::槽)

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