Qt中的字符串宏 | 编译期检查和运行期检查 | Qt信号与槽connect写法

一、什么是字符串宏(String Macro)

字符串宏其实来源于 C/C++ 的预处理器宏(Macro)

宏是什么?

在 C++ 代码正式编译之前,编译器会先经过一个预处理阶段, 在这个阶段,所有的 #define 宏定义都会被纯文本替换

例如:

cpp 复制代码
#define PI 3.14159

当你写:

cpp 复制代码
double area = PI * r * r;

预处理器会在编译前把它替换成:

cpp 复制代码
double area = 3.14159 * r * r;

这就是宏展开

那么什么是"字符串宏"?

字符串宏就是返回字符串的宏, 也就是说,它展开后不是数字或表达式,而是 字符串字面量(string literal)

例如:

cpp 复制代码
#define NAME "Landon"

当代码中出现:

cpp 复制代码
std::cout << NAME;

编译器看到的其实是:

cpp 复制代码
std::cout << "Landon";

Qt 中的 SIGNAL() / SLOT() 宏就是字符串宏

在 Qt4 以及早期 Qt5 中,信号和槽连接常写成这样:

cpp 复制代码
connect(spinBox, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));

表面上看像是函数调用,但其实:

  • SIGNAL()SLOT() 是两个宏;

  • 它们把括号内的内容变成 字符串常量

展开后是这样(伪代码):

cpp 复制代码
connect(spinBox, "2valueChanged(int)", label, "1setNum(int)");

"2""1" 是内部用于区分信号和槽的标识,Qt 的元对象系统(Meta-Object System)会在运行时根据这些字符串去查找信号和槽是否存在

关键点:

SIGNAL 和 SLOT 写法依赖字符串匹配,编译器并不会验证信号或槽的存在

举个例子

cpp 复制代码
connect(spinBox, SIGNAL(valueChaged(int)), label, SLOT(setNum(int)));

你不小心少写了一个 n(changed被你不小心写为chaged),程序依然能编译通过

但运行时会出现:

复制代码
QObject::connect: No such signal QSpinBox::valueChaged(int)

这就是字符串宏的弊端:

  • 编译器无法检查;

  • 错误要到运行时才发现。

Qt5+ 改进:函数指针写法取代字符串宏

Qt5 之后,我们可以写:

cpp 复制代码
connect(spinBox, &QSpinBox::valueChanged, label, &QLabel::setNum);

这种写法不再使用字符串,而是**真正的函数指针,**编译器能在编译期就检查函数是否存在、参数是否匹配,彻底杜绝拼写错误

二、编译期检查与运行期检查

在 C++(以及 Qt)编程中,你可能常听到这两个概念:

"这种写法只能运行期报错,而那种写法能在编译期就检查出来"

那么,究竟什么是 编译期检查(Compile-time Check)运行期检查(Runtime Check) 呢?它们的区别到底是什么?为什么 Qt 推荐我们使用新式connect语法?

这篇文章帮你彻底搞清楚

程序的两个阶段

当我们写一个 C++ 程序,它要经过两个重要阶段:

编译期(Compile-time)------ 程序还没运行,编译器正在"检查并生成机器代码"的阶段

运行期(Runtime)------ 程序已经被加载到内存中,正在实际执行的阶段

编译期检查(Compile-time Checking)

定义:

编译器在"编译阶段"发现问题,并阻止程序生成可执行文件

特点:

  • 错误在编译时就会报出

  • 不需要运行程序

  • 一旦出错,编译器立即停止编译

  • 错误更容易定位,更安全

举个例子:

cpp 复制代码
int x = "hello";   // ❌ 类型不匹配

编译器会直接报错:

cpp 复制代码
error: invalid conversion from 'const char*' to 'int'

这就是 编译期检查

在 Qt 中的例子:

旧版写法(Qt4 风格):

cpp 复制代码
connect(spinBox, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));

这行代码中的 SIGNAL()SLOT()字符串宏 ,编译器根本不知道 valueChanged(int)setNum(int) 具体是什么函数!

如果你写成:

复制代码
connect(spinBox, SIGNAL(valueChanged(QString)), label, SLOT(setNum(int)));

编译器也会 默默通过,但程序运行时会在控制台输出警告:

复制代码
QObject::connect: No such signal QSpinBox::valueChanged(QString)

这类错误只能 运行时 才被发现,属于运行期检查

运行期检查(Runtime Checking)

程序在运行过程中,才检测并发现错误

特点:

  • 编译器不会报错

  • 程序能编译通过

  • 错误只在程序执行时才暴露

  • 有时会导致程序崩溃或逻辑错误

  • 调试成本高

举个例子:

cpp 复制代码
int arr[3] = {1, 2, 3};
arr[5] = 10;   // ❌ 越界访问

编译器无法发现这个错误,因为它不知道你运行时会访问哪个索引

只有在运行时才会出错(甚至直接崩溃)

Qt 示例

新式写法(Qt5+ 推荐):

cpp 复制代码
connect(spinBox, 
        QOverload<int>::of(&QSpinBox::valueChanged), 
        label, 
        &QLabel::setNum);

这里使用了 函数指针 + 模板机制 ,编译器能在 编译期 就检查:

  • 这个信号是否存在;

  • 参数类型是否匹配;

  • 槽函数是否可调用。

如果你写错类型:

cpp 复制代码
connect(spinBox,
        QOverload<QString>::of(&QSpinBox::valueChanged),
        label,
        &QLabel::setNum);

编译器立即报错:

复制代码
error: no matching function for call to 'connect'

这就是 编译期检查

三、Qt信号与槽connect写法

Qt 的 connect() 是信号与槽机制的核心函数,从 Qt4 → Qt5 → Qt6connect() 的写法经历了三次重大进化

Qt 的信号槽机制简介

connect() 的作用是:

把一个 信号(signal) 连接到一个 槽(slot)可调用对象(callable)

当信号发出时,槽函数自动被调用

语法通用结构如下:

cpp 复制代码
connect(sender, signal, receiver, slot);
  • sender:发出信号的对象指针

  • signal:信号函数

  • receiver:接收信号的对象指针

  • slot:接收信号后执行的函数(槽)

Qt 各版本 connect 写法概览

Qt 版本 写法类型 是否类型安全 是否支持重载信号 示例
Qt4(老式) 宏字符串写法 ❌ 否(运行期检查) ❌ 否 connect(obj, SIGNAL(sig(int)), obj2, SLOT(slot(int)));
Qt5(推荐) 函数指针写法 ✅ 是(编译期检查) ✅ 是 connect(obj, &Class::signal, obj2, &Class2::slot);
Qt5(复杂信号) QOverload 辅助写法 ✅ 是 ✅ 是 connect(obj, QOverload<int>::of(&Class::signal), obj2, &Class2::slot);
Qt5(Lambda) Lambda 槽函数 ✅ 是 ✅ 是 connect(obj, &Class::signal, this, [=](int v){ ... });
Qt6(简化) 新式 connect(自动推导) ✅ 是 ✅ 是 connect(obj, &Class::signal, this, &MyClass::slot); 或省略 receiver

详细讲解每种写法

① 宏字符串写法(Qt4 风格)

cpp 复制代码
connect(spinBox, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));

特点:

  • 使用宏 SIGNAL()SLOT()

  • 宏会把参数变为字符串(如 "2valueChanged(int)"

  • Qt 运行时再用反射机制(MOC 系统)去匹配信号槽

缺点:

  • 运行期检查,拼写错误不会被编译器发现

  • 不支持信号重载

  • Qt5 中仍可用,但已被官方标记为不推荐

② 函数指针写法(Qt5 推荐)

Qt5 引入了 编译期类型检查的函数指针 connect

cpp 复制代码
connect(spinBox, &QSpinBox::valueChanged, label, &QLabel::setNum);

优点:

  • 编译期类型安全

  • 支持重载信号

  • 自动匹配函数签名

  • 拼写错误会编译不通过

  • 支持自动断开(随对象析构自动 disconnect)

原理:

Qt 利用 C++11 函数指针机制,让编译器能在编译时检查信号与槽参数类型是否兼容

③ 重载信号写法(QOverload)

某些 Qt 类的信号是 overload(重载) 的,比如 QSpinBox::valueChanged

cpp 复制代码
void valueChanged(int value);
void valueChanged(const QString &text);

这时候你必须告诉编译器你想连接哪个版本。

QOverload 写法:

cpp 复制代码
connect(spinBox,
        QOverload<int>::of(&QSpinBox::valueChanged),
        label,
        &QLabel::setNum);

或者:

cpp 复制代码
connect(spinBox,
        QOverload<const QString &>::of(&QSpinBox::valueChanged),
        this,
        &MyWidget::onTextChanged);

原理:

QOverload<Args...>::of(&Class::signal) 明确指定了信号参数类型

④ Lambda 槽函数写法(Qt5 新增)

Qt5 开始允许用 Lambda 表达式 作为槽函数,非常方便(如果会用的话)

cpp 复制代码
connect(spinBox, &QSpinBox::valueChanged, this, [=](int value){
    qDebug() << "SpinBox value:" << value;
});

优点:

  • 不需要定义槽函数

  • 支持捕获外部变量

  • 类型安全

  • 写法简洁

用法补充:

你可以省略 receiver,Qt 会自动推导:

cpp 复制代码
connect(spinBox, &QSpinBox::valueChanged, [=](int v){
    qDebug() << v;
});

但要注意:若无 receiver,lambda 的生命周期不受控,最好与 QObject::connect 的返回值配合使用来管理连接

⑤ Qt6 的新写法(自动推导 + 简化)

Qt6 在 Qt5 基础上进一步简化了 connect 语法,支持自动推导、可选参数:

cpp 复制代码
connect(spinBox, &QSpinBox::valueChanged, this, &MyWidget::updateLabel);

甚至:

cpp 复制代码
connect(spinBox, &QSpinBox::valueChanged, [=](int v){ qDebug() << v; });

或者返回连接对象:

cpp 复制代码
auto connection = connect(button, &QPushButton::clicked, this, &MyClass::onClick);
disconnect(connection);  // 随时解除连接

disconnect() 写法也有几种

写法 用法说明
disconnect(sender, signal, receiver, slot) 断开指定信号槽连接
disconnect(connection) Qt5 起支持,通过保存 connect 返回值断开
disconnect(sender, signal) 断开该信号的所有槽
disconnect() 断开该对象所有连接
相关推荐
孤独的追光者15 分钟前
使用Qt Designer开发上位机
开发语言·python·qt
m0_7369270423 分钟前
Java面试场景题及答案总结(2025版持续更新)
java·开发语言·后端·职场和发展
muyouking1128 分钟前
Rust + WASM + Svelte 深度实战:内存管理、性能权衡与图像处理进阶
开发语言·rust·wasm
无语子yyds38 分钟前
C++双指针算法例题
数据结构·c++·算法
羑悻的小杀马特41 分钟前
ProtoBuf语法揭秘:探秘编译魔法与性能优化策略,解锁多层级选项配置的底层奥秘
c++·编程·protobuf
fpcc43 分钟前
C++编程实践——eventFD
linux·c++
仟濹44 分钟前
「经典数字题」集合 | C/C++
c语言·开发语言·c++
Skrrapper1 小时前
【STL】set、multiset、unordered_set、unordered_multiset 的区别
c++·算法·哈希算法
lkbhua莱克瓦241 小时前
Java练习——正则表达式2
java·开发语言·笔记·正则表达式·github·学习方法
冷崖1 小时前
QML-Model-View
javascript·c++