title: 信号与槽 tags: [
嵌入式Linux,
Qt,
信号与槽,
signal,
slot,
connect,
Q_OBJECT,
moc,
元对象系统,
QtCreator,
快捷键,
IMX6ULL,
] created: 2026-09-18 updated: 2026-09-18
💡 关联知识:[[05-Qt应用开发/02-Qt入门与项目结构]](
Q_OBJECT、ui_mainwindow.h、Qt Designer 的可视化连接都在这一篇收口)、[[05-Qt应用开发/04-Qt控件详解]](每个控件都有大量内置信号与槽);延伸阅读:[[编程学习项目/使用Qt和C++构造UI界面/4. 信号和槽机制]]
信号与槽(Signal & Slot)是 Qt 编程的基础,也是 Qt 的一大创新。有了它,界面各组件之间的交互变得直观而简单。本篇从机制、自定义信号、自定义槽、连接、内置信号槽讲到 Qt Creator 的快捷键与帮助文档,并给出一个完整可运行的自定义信号槽例程。
本章起,教程不再使用 Qt Designer 开发,新建项目时取消勾选 *.ui 文件。原因是:控件少时 Designer 方便直观,但控件一多就不好管理,每次加控件都要打开 .ui;而且信号槽连接藏在生成的 setupUi() 里或设计器的连接模式里,不如代码直观。统一用代码画界面,能锻炼布局能力和代码逻辑能力。
| 概念 | 定义 | 例子 |
|---|---|---|
| 信号(Signal) | 在特定情况下被发射的事件 | PushButton 被鼠标单击时发射 clicked();ComboBox 列表项变化时发射 CurrentIndexChanged() |
| 槽(Slot) | 对信号响应的函数 | 与信号关联后,信号发射时自动执行 |
槽与普通 C++ 函数的相同点:可以定义在类的任何部分(public、private、protected),可以有任意参数,也可以被直接调用。不同点:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
GUI 程序设计的主要内容,就是对界面上各组件信号的响应——只需知道什么情况下发射哪些信号,合理地去响应和处理即可。
信号与槽关联用 QObject::connect() 函数实现:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
connect() 是 QObject 类的静态函数,而 QObject 是所有 Qt 类的基类,实际调用时可以忽略前面的限定符:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
| 参数 | 含义 |
|---|---|
sender |
发射信号的对象名称 |
SIGNAL(signal()) |
信号名称,信号可看作特殊函数,需要带括号,有参数时要写参数 |
receiver |
接收信号的对象名称 |
SLOT(slot()) |
槽函数名称,需要带括号,有参数时要写参数 |
SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并把它们的参数转换为相应的字符串。
在 Qt Designer 例程 02_designer_example 编译生成的 ui_mainwindow.h(位于 build 目录)里,setupUi() 中有这样一句:
QObject::connect(pushButton, SIGNAL(clicked()), MainWindow, SLOT(close()));
它的作用就是把 pushButton 的 clicked() 信号与窗体 MainWindow 的 close() 槽关联,所以单击按钮就会执行窗口的 close()。这解答了上一篇"可视化连接藏在哪里"的疑问——它在 UI 文件编译后生成的代码里。
| 规则 | 说明 | 示例 |
|---|---|---|
| 一个信号可连接多个槽 | 槽按连接建立的顺序依次执行 | connect(btn, SIGNAL(clicked()), this, SLOT(hide())); 与 connect(btn, SIGNAL(clicked()), this, SLOT(close())); |
| 多个信号可连接同一个槽 | 任一信号发射都执行该槽 | 三个 pushButton 的 clicked() 都连到 close() |
| 一个信号可连接另外一个信号 | 信号发射时也发射另一个信号 | connect(btn, SIGNAL(objectNameChanged(QString)), this, SIGNAL(windowTitleChanged(QString))); |
| 参数匹配 | 严格情况下信号与槽的参数个数和类型需一致,至少信号的参数不能少于槽的参数;不匹配会编译错误或运行错误 | 有参数的信号连接无参槽通常可以,反之不行 |
必须加 Q_OBJECT |
使用信号与槽的类,类定义中必须加入宏 Q_OBJECT(特别重要) |
见下方 mainwindow.h |
| 执行时机 | 信号发射时,关联槽通常被立即执行,像正常调用函数;所有槽执行完,才执行发射信号处后面的代码 | 顺序执行 |
"一个信号连多个槽"的补充:如果两个槽一个是 hide()、一个是 close(),由于 close() 写在后面且执行后程序退出,hide() 其实已无意义——但它确实先执行了,因为连接在前。
"一个信号连接另外一个信号"意味着 connect 万物皆可连,非常好用。
flowchart LR
subgraph 发送方
S["sender 对象"] --> SIG["signal() 信号"]
end
subgraph 接收方
R["receiver 对象"] --> SL["slot() 槽 / signal() 信号"]
end
SIG -->|connect| SL
要点:发送者与发送的信号在一起,接收者与接收的槽/信号在一起,顺序不能在 connect() 里写乱。
初学者基本用不到断开连接,用到时用 disconnect(),它被重载了好几个函数,典型原型:
bool QObject::disconnect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method)
| 需求 | 静态函数写法 | 非静态重载写法 |
|---|---|---|
断开与 myObject 连接的一切信号或槽 |
disconnect(myObject, 0, 0, 0); |
myObject->disconnect(); |
| 断开所有连接到特定信号的东西 | disconnect(myObject, SIGNAL(mySignal()), 0, 0); |
myObject->disconnect(SIGNAL(mySignal())); |
| 与指定的接收者断开连接 | disconnect(myObject, 0, myReceiver, 0); |
myObject->disconnect(myReceiver); |
其中 0 表示"任意匹配"(C 风格的 NULL)。
connect() 返回一个 QMetaObject::Connection 对象,可用于判断连接是否成功、或稍后断开设定的这一条连接:
QMetaObject::Connection c =
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
if (c) {
/* 连接成功 */
}
disconnect(c); // 只断开这一条连接
相比 disconnect(myObject, 0, 0, 0) 这种"一刀切"式断开,保存并断开具体的 Connection 更精确。
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
| 对比项 | 信号与槽 | 回调函数(函数指针) | Qt 事件(event) |
|---|---|---|---|
| 耦合度 | 发送者只负责 emit,不关心谁接收,低耦合 |
调用者必须持有并注册回调指针,耦合较高 | 由 QApplication 分发到目标对象 |
| 连接数量 | 一个信号可连多个槽;多个信号可连同一槽 | 通常一个回调只能挂一个函数 | 一个事件到达一个对象 |
| 类型安全 | 函数指针写法编译期检查;字符串写法运行期检查 | 编译期检查 | 需重写 event() 或具体 xxxEvent() |
| 跨线程 | 有 QueuedConnection 自动支持 |
需自行加锁/投递 | 需自行 postEvent |
| 典型场景 | 控件交互、模块解耦 | 标准库/底层库回调 | 重绘、鼠标键盘、自定义事件 |
信号槽本质上是 Qt 在"回调"之上加了一层元对象系统,换来松耦合 + 多对多 + 跨线程三个好处,代价是 moc 生成代码和轻微的性能开销。
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
原书第五章强调"使用信号槽的类必须加 Q_OBJECT"并提到了 ui_mainwindow.h 等生成文件,但未展开 moc 的内部原理。
信号在类里用 signals: 关键字声明,只写函数原型,不写函数体。这是因为信号不是由你实现的,而是由 Qt 的 moc(Meta-Object Compiler,元对象编译器) 根据声明自动生成实现代码。
流程如下:
Q_OBJECT 宏的头文件调用 moc,生成 moc_*.cpp;moc_*.cpp 与你的 *.cpp 一起编译链接。
flowchart LR
H["mainwindow.h\nclass MainWindow : QObject { Q_OBJECT; signals: ... }"] -->|moc| M["moc_mainwindow.cpp\n元对象表 + 信号实现 + qt_metacall"]
M --> C["与 mainwindow.cpp 一起编译"]
C --> BIN["可执行程序"]
| 生成内容 | 作用 |
|---|---|
staticMetaObject |
类的元对象,保存类名、信号槽签名、父类信息等 |
| 信号的实现函数 | 调用 QMetaObject::activate() 发射信号 |
qt_metacall 等 |
按索引分发信号与槽调用 |
metaObject() 覆写 |
让运行时能查询类型信息 |
因此 Qt 能在运行时通过字符串签名(SIGNAL(...)/SLOT(...) 宏最终转为字符串)查找并调用对应函数,这就是信号槽跨对象、跨线程也能工作的基础。
Q_OBJECT:启用元对象系统,位置必须在类定义的第一行。signals::声明信号区,映射为 public,只能声明不能定义。slots: / public slots: / private slots::声明槽区,本质是普通函数,加宏后可被 connect 按字符串查找。emit:发射信号的关键字,其实是空宏,仅用于语义标记;也可以省略不写,但不建议。Q_OBJECT 缺失是新手最常见的错误之一,症状往往是 connect 编译通过但运行时不触发,或报 undefined reference to vtable。新建项目 03_signal_slot_example,步骤同 3.6,取消勾选 *.ui 文件(原因见本章开头)。
信号只需声明,无需定义,所以只需在 mainwindow.h 里声明即可。下面创建一个 void pushButtonTextChanged(); 信号——命名最好贴合信号本身的含义,这个信号表示"按钮文本发生改变后"。
mainwindow.h(添加信号后):
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
/* 引入QPushButton */
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
/* 声明一个信号,只需声明,无需定义 */
void pushButtonTextChanged();
};
#endif // MAINWINDOW_H
说明(第 16~18 行):声明一个信号 void pushButtonTextChanged();,可以看到信号无需 public 等关键字修饰。
创建槽的方法:在 mainwindow.h 里声明槽,在 mainwindow.cpp 里实现槽。声明槽必须写定义(函数体实现),否则编译会报错。
槽的特点:
由于第 5.2 节声明的信号 void pushButtonTextChanged(); 无返回值、无参数,所以声明的槽函数也必须 void 且无参数。此外再声明一个 QPushButton 对象 pushButton(可简写 btn,按习惯即可,只要不让人看不懂),以及一个处理按钮点击的槽。
mainwindow.h(添加槽函数后):
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
/* 引入QPushButton */
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
/* 声明一个信号,只需声明,无需定义 */
void pushButtonTextChanged();
public slots:
/* 声明一个槽函数 */
void changeButtonText();
/* 声明按钮点击的槽函数 */
void pushButtonClicked();
private:
/* 声明一个对象pushButton */
QPushButton *pushButton;
};
#endif // MAINWINDOW_H
说明:
public slots::声明一个槽函数 void changeButtonText();。void pushButtonClicked();。QPushButton 对象 pushButton。在 mainwindow.cpp 里实现这两个槽,并实例化 pushButton:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置窗体的宽为800,高为480 */
this->resize(800,480);
/* 实例化pushButton对象 */
pushButton = new QPushButton(this);
/* 调用setText()方法设定按钮的文本 */
pushButton->setText("我是一个按钮");
}
MainWindow::~MainWindow()
{
}
/* 实现按钮点击槽函数 */
void MainWindow::pushButtonClicked()
{
/* 使用emit发送信号 */
emit pushButtonTextChanged();
}
/* 实现按钮文本改变的槽函数 */
void MainWindow::changeButtonText()
{
/* 在槽函数里改变按钮的文本 */
pushButton->setText("被点击了!");
}
逐行解释:
pushButton,并指定父对象为 this。因为头文件里声明的是指针类型,所以用 new。QPushButton(this) 的原型其实是 QPushButton(QWidget *parent = nullptr)。setText() 设定按钮文本。emit 发送自定义信号 pushButtonTextChanged()。QPushButton 的构造函数有多个重载(C++ 重载在 Qt 中的体现):
QPushButton(QWidget *parent = nullptr)
QPushButton(const QString &text, QWidget *parent = nullptr)
QPushButton(const QIcon &icon, const QString &text, QWidget *parent = nullptr)
第 5.2、5.3 节只是声明了信号和定义了槽,要真正生效还需 connect:
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
connect(this, SIGNAL(pushButtonTextChanged()), this, SLOT(changeButtonText()));
如何判断发送者和接收者该写谁?
pushButtonClicked() 是本类定义的槽,changeButtonText() 也是本类的槽 → 接收者写 this。pushButtonTextChanged() 是本类定义的信号 → 发送者也写 this。03_signal_slot_example.pro(注意 FORMS += 后为空,因为不使用 ui):
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS +=
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
/* 引入QPushButton */
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
/* 声明一个信号,只需声明,无需定义 */
void pushButtonTextChanged();
public slots:
/* 声明一个槽函数 */
void changeButtonText();
/* 声明按钮点击的槽函数 */
void pushButtonClicked();
private:
/* 声明一个对象pushButton */
QPushButton *pushButton;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置窗体的宽为800,高为480 */
this->resize(800,480);
/* 实例化pushButton对象 */
pushButton = new QPushButton(this);
/* 调用setText()方法设定按钮的文本 */
pushButton->setText("我是一个按钮");
/* 信号与槽连接 */
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
connect(this, SIGNAL(pushButtonTextChanged()), this, SLOT(changeButtonText()));
}
MainWindow::~MainWindow()
{
}
/* 实现按钮点击槽函数 */
void MainWindow::pushButtonClicked()
{
/* 使用emit发送信号 */
emit pushButtonTextChanged();
}
/* 实现按钮文本改变的槽函数 */
void MainWindow::changeButtonText()
{
/* 在槽函数里改变按钮的文本 */
pushButton->setText("被点击了!");
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
第 16、17 行完成连接后,整个流程如下:
sequenceDiagram
participant U as 用户
participant B as pushButton
participant M as MainWindow
U->>B: 单击按钮
B->>M: clicked() 信号
M->>M: pushButtonClicked() 槽执行
M->>M: emit pushButtonTextChanged()
M->>M: changeButtonText() 槽响应
M->>B: setText("被点击了!")
B-->>U: 按钮文本改变
即:点击按钮 → 触发 pushButtonClicked() → 槽里发送 pushButtonTextChanged() 信号 → changeButtonText() 槽响应 → 按钮文本由"我是一个按钮"变成"被点击了!"。
cd 03_signal_slot_example
/opt/Qt5.12.9/5.12.9/gcc_64/bin/qmake 03_signal_slot_example.pro
make
./03_signal_slot_example
或在 Qt Creator 中直接 Ctrl + R。程序虽简单,但完整实现了自定义信号与自定义槽。
Qt 里有大量自定义好的信号与槽,基本够用;找不到想要的信号与槽时,再按 5.2~5.4 节自定义。
怎么知道某个类有哪些信号与槽?
connect 语句里按住 Ctrl 再点击 clicked(),跳转到该信号的定义处。例如进入 QPushButton 的定义,可看到它不止 clicked,还有很多其他信号和槽函数。返回上一步用 Alt + 左方向键。⚠️ 来源说明:本节不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
原书第五章只讲解 Qt4/字符串式 SIGNAL/SLOT 写法,并声明"Qt4 与 Qt5 写法有区别、本教程对 Qt4 写法不做讲解"。本节介绍的函数指针写法、lambda 槽、连接类型为 Qt5 扩展知识。
写法一:字符串签名(原书用法,继续有效)
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
写法二:函数指针(Qt5 推荐,编译期检查)
connect(pushButton, &QPushButton::clicked, this, &MainWindow::pushButtonClicked);
SIGNAL/SLOT 宏,不需要写参数类型字符串。qOverload 或 static_cast 消歧,例如 &QComboBox::currentIndexChanged 有重载版本。| 对比项 | SIGNAL/SLOT 字符串写法 |
函数指针写法 |
|---|---|---|
| 错误检查时机 | 运行期 | 编译期 |
是否需要 SIGNAL/SLOT |
需要 | 不需要 |
| 重载信号 | 字符串自动区分 | 需 qOverload/static_cast |
| 可否给槽传 lambda | 否 | 可以 |
| Qt5/Qt6 支持 | 支持(保留) | 推荐 |
函数指针写法下,接收者可以省略,直接用 lambda 作为槽:
connect(pushButton, &QPushButton::clicked, this, [=]() {
pushButton->setText("被点击了!");
});
[=] 按值捕获外部变量,[this] 捕获当前对象指针。SIGNAL/SLOT 字符串写法连接。connect 的第 5 个可选参数控制投递方式:
connect(sender, SIGNAL(sig()), receiver, SLOT(slot()), Qt::ConnectionType type);
| 枚举值 | 行为 | 线程关系要求 |
|---|---|---|
Qt::AutoConnection(默认) |
发送者与接收者同线程→Direct;跨线程→Queued | 通用 |
Qt::DirectConnection |
立即在发送者所在线程直接调用槽 | 任意 |
Qt::QueuedConnection |
把调用封装为事件投递到接收者线程的事件队列,异步执行 | 接收者线程须有事件循环 |
Qt::BlockingQueuedConnection |
同 Queued,但发送者阻塞直到槽执行完 | 同线程使用会死锁 |
Qt::UniqueConnection |
与上述之一按位或使用,防止重复连接同一对信号槽 | 通用 |
flowchart TD
SIG["信号发射"] --> SAME{"发送者与接收者\n是否同线程?"}
SAME -->|同线程| D["DirectConnection\n立即调用"]
SAME -->|跨线程| Q["QueuedConnection\n投递到接收者事件队列"]
Q --> EV["接收者事件循环取出\n在接收者线程执行槽"]
要点:默认 AutoConnection 已能覆盖绝大多数场景;跨线程时 Qt 自动用队列连接,因此不要手动 new 线程对象后直接在错误线程调用其函数。UniqueConnection 常用来避免同一连接被建立多次导致槽被重复执行。
想知道某个按钮的快捷键,把鼠标移到按钮上即可看到提示(需在英文输入法下)。
一般快捷键
| 功能 | 快捷键 |
|---|---|
| 新建文件或项目(N) | Ctrl + N |
| 关闭当前窗口/文件 | Ctrl + W |
| 关闭所有文件 | Ctrl + Shift + W |
| 关闭当前文件(Windows) | Ctrl + F4 |
| 运行 | Ctrl + R |
| 返回上一级(常用于跳转代码) | Alt + ← |
| 进入下一级(常用于跳转代码) | Alt + → |
| Qt 自动排版对齐代码 | Ctrl + I |
| 减小字体 | Ctrl + -(或 Ctrl + 滚轮向下) |
| 增大字体 | Ctrl + +(或 Ctrl + 滚轮向上) |
| 重置字体大小 | Ctrl + 0 |
| 折叠 | Ctrl + < |
常用编辑快捷键
| 功能 | 快捷键 |
|---|---|
| 展开 | Ctrl + > |
| 复制行 | Ctrl + Ins |
| 复制到行下 | Ctrl + Alt + Down |
| 复制到行上 | Ctrl + Alt + Up |
| 在当前行上方插入新行 | Ctrl + Shift + Enter |
| 在当前行下方插入新行 | Ctrl + Enter |
| 查看剪切板历史 | Ctrl + Shift + V |
| 剪切行 | Shift + Del |
| 追加行 | Ctrl + J |
| 向下移动当前行 | Ctrl + Shift + Down |
| 向上移动当前行 | Ctrl + Shift + Up |
| 切换函数声明/定义 | Ctrl + 鼠标左键 / Shift + F2 |
| 编辑信号和槽 | F4 |
跳转至以 } 结尾的块 |
Ctrl + } |
跳转至以 { 开始的块 |
Ctrl + { |
| 打开类型层次窗口 | Ctrl + Shift + T |
快捷键可能与 Ubuntu/Windows 系统冲突。可在"工具 → 选项 → 环境 → 键盘"里修改:选中命令,按"Record"重新记录新组合键,再 Apply / OK。注意不要与系统快捷键重复(例如 Ctrl + M 显示红色即表示与系统冲突)。没有定义快捷键的命令,也可以按同样步骤自定义。
Qt 帮助文档在安装时已随 Qt 一起安装,位于"安装目录 / Qt5.12.9 / Docs /"下(html 文本,也可用浏览器打开);Qt Creator 内置了同一套文档。
在 Qt Creator 中查文档的步骤:
QPushButton,也可只输入 QPush 匹配。如何阅读一个类的文档页:
| 标题 | 内容 |
|---|---|
| Properties | 类有哪些属性 |
| Public Functions | 类有哪些公开函数(方法) |
| Public Slots | 类有哪些可用槽 |
| Signals | 类有哪些信号 |
| Detailed Description | 详细说明与示例 |
查看某个方法的用法,直接单击进入即可。记得用 Alt + ← / Alt + → 在上下级文档间跳转。
Qt 帮助文档是最权威、最全面的中文/英文资料,任何 Qt 教程都没有它详细(尽管是英文);Qt 还自带大量示例可以参考。看文档要多练,并不是看会了就会了,实践起来才知道。
flowchart LR
A["不知道 QPushButton 有什么信号槽"] --> B["帮助 → 索引 → 输入 QPush"]
B --> C["进入类文档页"]
C --> D["Properties / Public Functions"]
C --> E["Signals / Public Slots"]
E --> F["Ctrl 点击 connect 中的函数名直接跳转"]
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 点击按钮没反应 | 漏写 connect,或连接写反了发送者/接收者 |
检查 connect(sender, SIGNAL(...), receiver, SLOT(...)) 顺序;确认在 ui->setupUi(this) 之后连接 |
编译报 undefined reference to vtable for ... |
类里写了 Q_OBJECT 却没重新运行 qmake/moc |
Qt Creator 里执行"构建 → 执行 qmake",再重新构建;确认 .pro 里 HEADERS 包含该头文件 |
运行时报 QObject::connect: No such signal/slot |
SIGNAL/SLOT 字符串拼写错误、参数不一致、忘写括号 |
用 Ctrl 点击跳转核对签名;优先改用函数指针写法让编译期报错 |
| 槽执行了两次 | 同一对信号槽被 connect 了多次 | 用 Qt::UniqueConnection,或检查是否重复初始化 |
| 跨线程槽里操作界面崩溃 | 在子线程直接调用了 UI 对象 | 用信号槽(默认 AutoConnection)把结果投递回主线程再更新界面 |
自定义信号编译报 error: no matching function |
信号只声明未定义?其实正常 | 信号本就不该写函数体;检查是否误把信号写成普通函数、或忘加 signals: 区 |
| 中文字符显示乱码 | 源文件编码或未配置中文输入 | 见 [[05-Qt应用开发/02-Qt入门与项目结构]] 的中文环境/输入法配置 |
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
答:信号是在特定情况下被发射的事件(如按钮被点击发射 clicked());槽是对信号响应的函数。槽本身就是普通 C++ 函数,可以定义在 public/protected/private 下、可以有任意参数、也可以被直接调用;它的特殊之处仅在于能与信号关联,信号发射时被自动执行。信号则只声明不定义,由 moc 生成实现。
Q_OBJECT 宏?信号为什么只声明不实现?答:Q_OBJECT 宏启用 Qt 的元对象系统,moc 依据它扫描并生成 moc_*.cpp,其中包含元对象表、信号的实现(内部调用 QMetaObject::activate)以及按索引分发调用的 qt_metacall 等。正因为信号由 moc 自动实现,类里只写函数原型即可,无需(也不能)自己写函数体。缺少 Q_OBJECT 会导致信号槽无法工作,常见报错是 undefined reference to vtable 或运行时 "No such signal/slot"。
SIGNAL/SLOT 宏写法与函数指针写法有什么区别?答:SIGNAL/SLOT 宏把函数签名转成字符串,connect 在运行期按字符串查找匹配,编译期不检查,拼写错误要到运行时才发现;函数指针写法(如 connect(btn, &QPushButton::clicked, this, &MainWindow::slot))在编译期就能检查信号/槽是否存在、参数是否匹配,更安全,也能连接 lambda 作为槽;重载信号时函数指针需要 qOverload 或 static_cast 消歧。
答:槽函数的参数和返回值要与信号一致;严格情况下二者参数个数和类型需匹配,至少信号的参数不能少于槽的参数,否则会编译或运行错误。一个信号可以连接多个槽,也可以连接另一个信号;多个信号也可以连接同一个槽。一个信号连多个槽时,槽按连接建立时的顺序依次执行,并且所有槽执行完才继续执行发射信号处后面的代码(默认直接调用情形)。
Qt::AutoConnection 的行为是什么?答:connect 的第五个参数可指定连接类型。默认 Qt::AutoConnection 会判断发送者与接收者是否在同一线程:同线程按 DirectConnection 立即在发送者线程调用槽;跨线程按 QueuedConnection 把调用封装成事件投递到接收者线程的事件队列,由接收者的事件循环取出执行,从而保证槽在接收者线程执行,避免竞态。BlockingQueuedConnection 会阻塞发送者直到槽执行完,但不能用于同一线程,否则死锁;UniqueConnection 与其它类型按位或,可防止重复连接。
内容来源:《I.MX6U嵌入式Qt开发指南V1.2》第五章 Qt信号与槽、第六章 Qt Creator的使用技巧,以及正点原子 Qt 开发指南例程源码 Embedded-Qt-Tutorial/Qt/01/03_signal_slot_example。