一、什么是字符串宏(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 → Qt6 ,connect() 的写法经历了三次重大进化
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() | 
断开该对象所有连接 |