简单易懂的计数器(理解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(发送者, &类名::信号, 接收者, &类名::槽)

相关推荐
尚墨11112 小时前
Java RestTemplate报错Invalid mime type “charset=utf-8“: does not contain ‘/‘
java·开发语言
我命由我123452 小时前
Java 开发使用 MyBatis PostgreSQL 问题:传入的参数为 null,CONCAT 函数无法推断参数的数据类型
java·开发语言·数据库·学习·postgresql·mybatis·学习方法
木心爱编程2 小时前
Qt C++ Excel 文件解析与导出实战:QAxObject 封装工具类
c++·qt·数据库开发
爱装代码的小瓶子2 小时前
【c++知识铺子】map和set的底层-红黑树
java·开发语言·c++
洛阳泰山2 小时前
Java实现周易六爻自动排盘:根据卜卦的时间推算出天干地支
java·开发语言·周易·六爻
apihz2 小时前
随机英文姓名生成API接口详细教程:免费、简单、高效
android·java·运维·服务器·开发语言
识途老码2 小时前
python开启ssh端口转发
开发语言·python·ssh
曹牧2 小时前
Java:类的前20个字段转换为Json
java·开发语言·python
枫叶丹42 小时前
【Qt开发】Qt窗口(八) -> QFileDialog 文件对话框
c语言·开发语言·数据库·c++·qt