首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从零开始的Qt开发指南:(四)Qt 信号与槽拓展:从自定义到连接方式,带你彻底掌握信号与槽的本质

从零开始的Qt开发指南:(四)Qt 信号与槽拓展:从自定义到连接方式,带你彻底掌握信号与槽的本质

作者头像
_OP_CHEN
发布2026-01-14 10:54:25
发布2026-01-14 10:54:25
1450
举报
文章被收录于专栏:C++C++

前言

在上一期Qt的博客中,我为大家介绍了信号与槽的原理与基础使用,接下来本文将围绕 Qt 信号与槽的核心知识点展开,从基础语法、带参数的信号槽设计,到多样化的连接方式,再到断开连接、Qt4 兼容、Lambda 槽函数等高级用法,最后深入分析其优缺点与实战选型建议。下面就让我们正式开始吧!


一、信号与槽的核心概念:Qt 的 "通信魔法"

在正式讲解语法之前,我们先来回顾一下信号与槽的概念 —— 信号与槽是什么?

简单来说:

  • 信号(Signal):是组件在特定事件发生时发出的 "通知"。比如按钮被点击(clicked())、滑块位置变化(valueChanged(int))、窗口关闭(closed())等,都是 Qt 内置的信号。除此之外,我们也可以根据业务需求自定义信号。
  • 槽(Slot):是接收信号并处理相应逻辑的 "函数"。槽函数本质上就是普通的 C++ 成员函数,既可以是 Qt 提供的内置槽(如QWidget::close()),也可以是我们自己编写的自定义槽。
  • 连接(Connect):是将 "信号" 与 "槽" 绑定的过程。通过QObject::connect()函数,我们可以指定:当某个对象发出某个信号时,哪个对象的哪个槽函数会被自动调用。

信号与槽的核心优势在于松耦合:发送信号的组件完全不需要知道接收信号的组件是谁,也不需要知道对方如何处理信号;接收组件的槽函数也不需要关心信号来自哪里。这种解耦特性让代码结构更清晰、维护成本更低,尤其在大型项目中优势极为明显。

在 Qt 中,要使用信号与槽机制,必须满足一个前提:所有涉及信号或槽的类,必须继承自QObject,并且在类定义中添加Q_OBJECT。这个宏的作用是让 Qt 的 MOC(Meta-Object Compiler,元对象编译器)为类生成元对象代码,从而支持信号发送、槽调用等动态特性。

二、自定义信号和槽:从基础到进阶

Qt 提供了大量内置信号和槽,但实际开发中,自定义信号和槽才是满足复杂业务需求的关键。本节将分两部分讲解:无参数的基础用法,以及带参数的进阶用法。

2.1 基本语法:无参数信号与槽的实现

自定义信号和槽的实现流程可概括为:定义类(继承 QObject + 添加 Q_OBJECT 宏)→ 声明信号 → 声明并实现槽函数 → 连接信号与槽 → 触发信号

基础格式(Qt5)如下:

代码语言:javascript
复制
QObject::connect(
    发送者对象指针,  // 谁发送信号
    &发送者类名::信号名,  // 发送什么信号(函数指针形式)
    接收者对象指针,  // 谁接收信号
    &接收者类名::槽函数名  // 接收后执行什么槽函数(函数指针形式)
);

这种连接方式是 Qt5 推荐的,类型安全(编译时检查信号和槽的匹配性),且支持重构(修改函数名时 IDE 会自动更新)。

2.2 带参数的信号和槽:传递数据的核心方式

在实际开发中,信号往往需要携带数据,槽函数需要根据这些数据执行不同的逻辑。例如:学生提交作业时告知作业分数,老师根据分数给出不同评语;滑块移动时传递当前位置值,标签显示该值。

带参数的信号和槽的核心规则是:信号的参数个数 ≥ 槽函数的参数个数,且对应位置的参数类型必须一致(或可隐式转换)

2.2.1 带参数信号槽的关键规则

  1. 参数个数匹配规则
    • 信号的参数个数可以大于或等于槽函数的参数个数,但不能少于。
    • 例如:信号有 2 个参数,槽函数可以有 2 个、1 个或 0 个参数(忽略所有参数);但如果信号有 1 个参数,槽函数不能有 2 个参数(编译报错)。
    • 当参数个数不匹配时,槽函数会接收信号的前 N 个参数(N 为槽函数的参数个数),忽略后面的参数。
  2. 参数类型匹配规则
    • 对应位置的参数类型必须完全一致,或可隐式转换(如intdoubleQStringconst char*)。
    • 错误示例:信号参数为int,槽函数参数为QString(无法隐式转换,编译报错)。
    • 建议:尽量使用完全匹配的参数类型,避免依赖隐式转换,提高代码可读性和稳定性。
  3. 参数传递方向
    • 信号的参数是 "输入" 到槽函数的,即信号发送时,参数值被传递给槽函数,槽函数可以读取这些值,但不能修改信号的参数(槽函数的参数是拷贝传递,除非使用引用)。
    • 注意:如果信号参数是引用类型(如const QString &),槽函数参数也应使用引用类型,避免不必要的拷贝(尤其对于大数据类型,如QImage、自定义结构体)。

2.3 自定义信号槽的常见问题排查

编译错误:undefined reference to vtable for XXX

  • 原因:忘记添加Q_OBJECT宏,或添加后未运行 qmake
  • 解决:在类定义开头添加Q_OBJECT,右键项目→"运行 qmake",然后重新编译。

信号触发后槽函数不执行

原因 1:信号与槽未正确连接(如对象指针为空、信号 / 槽函数名写错)。

解决:检查connect函数的四个参数是否正确,可通过connect的返回值判断连接是否成功:

代码语言:javascript
复制
bool isConnected = QObject::connect(student, &Student::homeworkSubmitted, teacher, &Teacher::correctHomework);
qDebug() << "连接是否成功:" << isConnected; // 输出true表示连接成功

原因 2:发送者或接收者对象被提前销毁(如栈对象超出作用域)。

解决:确保信号触发时,发送者和接收者对象仍然存在(建议使用智能指针QSharedPointer管理内存)。

参数不匹配导致编译报错

  • 错误提示:no matching function for call to 'QObject::connect(...)'
  • 解决:检查信号和槽的参数个数、类型是否匹配,尤其注意引用、const 修饰符的一致性。

三、信号与槽的连接方式:灵活控制通信行为

Qt 的connect函数不仅能实现基本的信号槽绑定,还支持通过连接类型(Connection Type) 控制槽函数的调用时机和线程行为。Qt 提供了 5 种内置连接类型,默认情况下会根据发送者和接收者是否在同一线程自动选择。

3.1 连接类型的枚举定义(Qt::ConnectionType)

代码语言:javascript
复制
enum ConnectionType {
    AutoConnection,        // 默认:自动选择(同一线程用Direct,不同线程用Queued)
    DirectConnection,      // 直接连接:信号发送时立即调用槽函数(同步执行,在发送者线程)
    QueuedConnection,      // 队列连接:将信号放入接收者线程的事件队列,异步执行(在接收者线程)
    BlockingQueuedConnection, // 阻塞队列连接:同Queued,但发送者线程会阻塞直到槽函数执行完毕
    UniqueConnection       // 唯一连接:确保信号与槽只连接一次,避免重复连接
};

3.2 各种连接类型的详细解析

3.2.1 AutoConnection(默认连接)

适用场景:大多数默认场景,无需手动指定。

行为逻辑

  • 如果发送者和接收者在同一线程:等同于DirectConnection(同步调用)。
  • 如果发送者和接收者在不同线程:等同于QueuedConnection(异步调用)。

优势:无需关心线程问题,Qt 自动适配,简化开发。

示例

代码语言:javascript
复制
// 默认连接(省略第五个参数)
QObject::connect(student, &Student::homeworkSubmitted, teacher, &Teacher::correctHomework);
// 等价于
QObject::connect(student, &Student::homeworkSubmitted, teacher, &Teacher::correctHomework, Qt::AutoConnection);
3.2.2 DirectConnection(直接连接)

  • 适用场景:需要槽函数立即执行,且发送者和接收者在同一线程(避免线程安全问题)。
  • 行为逻辑:信号发送时,槽函数会立即在发送者线程中同步执行,相当于直接调用槽函数。
  • 特点
    • 同步执行:发送者的代码会阻塞直到槽函数执行完毕。
    • 线程不安全:如果接收者在其他线程,直接调用会导致跨线程访问(如操作 UI 组件),引发崩溃。
  • 示例
代码语言:javascript
复制
// 直接连接(同一线程场景)
QObject::connect(student, &Student::homeworkSubmitted, teacher, &Teacher::correctHomework, Qt::DirectConnection);
3.2.3 QueuedConnection(队列连接)

适用场景:发送者和接收者在不同线程(如后台线程发送信号,UI 线程更新界面)。

行为逻辑

  • 信号发送时,Qt 会将信号包装成一个事件,放入接收者线程的事件队列中。
  • 接收者线程的事件循环会在合适的时机取出事件,调用槽函数(异步执行)。

特点

  • 异步执行:发送者线程不会阻塞,继续执行后续代码。
  • 线程安全:槽函数在接收者线程执行,避免跨线程访问问题(如 UI 组件必须在主线程更新)。

注意事项

  • 信号的参数必须是可序列化的(即支持 Qt 的元对象系统,如基本类型、QString、QList 等,或自定义类型需使用Q_DECLARE_METATYPE声明)。
  • 示例(多线程场景):
代码语言:javascript
复制
#include <QThread>

// 后台工作线程类
class Worker : public QObject
{
    Q_OBJECT
public slots:
    void doWork() {
        // 模拟耗时操作
        QThread::sleep(2);
        emit workFinished("耗时操作完成!"); // 发送信号
    }
signals:
    void workFinished(const QString &result);
};

// UI线程中的接收者(如主窗口)
class MainWindow : public QWidget
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        Worker *worker = new Worker();
        QThread *thread = new QThread();

        // 将worker移动到子线程
        worker->moveToThread(thread);

        // 连接:子线程的信号 → 主线程的槽函数(队列连接,异步更新UI)
        connect(worker, &Worker::workFinished, this, &MainWindow::updateUI, Qt::QueuedConnection);

        // 启动线程并执行工作
        connect(thread, &QThread::started, worker, &Worker::doWork);
        thread->start();
    }

public slots:
    void updateUI(const QString &result) {
        qDebug() << "UI更新:" << result; // 在主线程执行,安全操作UI
    }
};
3.2.4 BlockingQueuedConnection(阻塞队列连接)

适用场景:发送者需要等待接收者处理完信号后再继续执行(如主线程等待子线程完成初始化)。

行为逻辑

  • QueuedConnection类似,槽函数在接收者线程异步执行。
  • 区别:发送者线程会阻塞,直到槽函数执行完毕后才继续运行。

关键注意事项

  • 发送者和接收者不能在同一线程!否则会导致死锁(发送者阻塞等待槽函数执行,而槽函数需要在发送者线程执行,永远无法触发)。
  • 示例:
代码语言:javascript
复制
// 主线程发送信号,子线程接收(阻塞主线程直到子线程处理完毕)
connect(mainObj, &MainObject::startInit, workerObj, &Worker::init, Qt::BlockingQueuedConnection);
3.2.5 UniqueConnection(唯一连接)

适用场景:避免同一信号与槽被重复连接(重复连接会导致槽函数被多次调用)。

行为逻辑

  • 如果信号与槽已经存在连接,再次调用connect时会返回false,不会创建新的连接。
  • 可以与其他连接类型组合使用(通过按位或|)。

示例

代码语言:javascript
复制
// 唯一连接 + 自动连接(避免重复连接)
bool ok1 = connect(student, &Student::homeworkSubmitted, teacher, &Teacher::correctHomework, Qt::UniqueConnection);
bool ok2 = connect(student, &Student::homeworkSubmitted, teacher, &Teacher::correctHomework, Qt::UniqueConnection);
qDebug() << ok1; // true(第一次连接成功)
qDebug() << ok2; // false(第二次连接失败,已存在)

3.3 连接方式的实战选型建议

场景

推荐连接类型

核心原因

同一线程、需同步执行

DirectConnection / AutoConnection

响应迅速,无额外开销

不同线程、UI 更新

QueuedConnection

线程安全,避免 UI 崩溃

发送者需等待接收者完成

BlockingQueuedConnection

同步等待结果,确保逻辑顺序

避免重复连接

UniqueConnection

防止槽函数多次调用

四、信号与槽的高级用法:断开连接、Qt4 兼容与 Lambda 槽

4.1 信号与槽的断开连接(disconnect)

在某些场景下,我们需要取消信号与槽的绑定(如对象销毁前、业务状态变化时),此时可以使用QObject::disconnect()函数。

4.1.1 disconnect 的四种用法

断开指定的信号与槽连接(最常用):

代码语言:javascript
复制
// 格式与connect对应,断开student的homeworkSubmitted信号与teacher的correctHomework槽的连接
QObject::disconnect(student, &Student::homeworkSubmitted, teacher, &Teacher::correctHomework);

断开发送者的所有信号连接

代码语言:javascript
复制
// 断开student对象发出的所有信号的所有连接
QObject::disconnect(student, nullptr, nullptr, nullptr);

断开发送者的某个信号的所有连接

代码语言:javascript
复制
// 断开student的homeworkSubmitted信号的所有连接(无论连接到哪个接收者和槽)
QObject::disconnect(student, &Student::homeworkSubmitted, nullptr, nullptr);

断开接收者的所有槽连接

代码语言:javascript
复制
// 断开所有信号与teacher对象的所有槽的连接
QObject::disconnect(nullptr, nullptr, teacher, nullptr);
4.1.2 断开连接的注意事项
  • 断开连接后,信号触发时槽函数不再执行
  • 如果发送者或接收者对象被销毁,Qt 会自动断开与该对象相关的所有信号槽连接,无需手动处理(避免野指针问题)。
  • disconnect的返回值为booltrue表示断开成功(存在对应的连接),false表示断开失败(无对应连接)。
4.1.3 示例:动态连接与断开

4.2 Qt4 版本信号与槽的连接方式(兼容旧代码)

Qt5 推出了基于函数指针的连接方式(如&Student::homeworkFinished),而 Qt4 使用的是基于字符串的连接方式(SIGNAL()SLOT()宏)。虽然 Qt5 完全兼容 Qt4 的语法,但由于字符串方式存在明显缺陷,仅推荐在维护旧代码时使用。

4.2.1 Qt4 连接语法格式
代码语言:javascript
复制
QObject::connect(
    发送者,
    SIGNAL(信号名(参数类型)),  // 注意:参数只写类型,不写变量名
    接收者,
    SLOT(槽函数名(参数类型))   // 同理,参数只写类型
);
4.2.2 示例(Qt4 语法)

(1)在头文件"widget.h"中声明信号与槽:

(2)在"widget.cpp"中实现槽函数并连接信号与槽:

4.2.3 Qt4 与 Qt5 连接方式的对比

特性

Qt4(字符串方式)

Qt5(函数指针方式)

类型安全

无(运行时检查,拼写错误导致槽函数不执行)

有(编译时检查,错误直接编译报错)

重构支持

无(修改函数名后字符串不会自动更新)

有(IDE 可自动重构,函数名修改同步更新)

参数写法

仅写类型(如SIGNAL(valueChanged(int)))

写完整函数指针(如&QSlider::valueChanged)

支持 Lambda

不支持

支持

兼容性

Qt4/Qt5 均支持

仅 Qt5 及以上支持

4.2.4 为什么不推荐 Qt4 语法?
  • 隐藏错误:如果信号或槽函数名拼写错误(如SIGNAL(homeworkSubmited())少写一个t),编译时不会报错,但运行时槽函数无法触发,排查难度大。
  • 性能损耗:字符串解析需要额外的运行时开销,不如函数指针直接高效。
  • 不支持 Lambda:无法使用简洁的 Lambda 表达式作为槽函数。

因此,新代码一律使用 Qt5 的函数指针方式,仅在维护 Qt4 遗留代码时使用 Qt4 语法。

4.3 使用 Lambda 表达式定义槽函数(Qt5+)

Qt5 引入了对 C++11 Lambda 表达式的支持,允许直接在connect函数中定义槽函数的逻辑,无需单独声明槽函数。这种方式特别适合简单的槽逻辑(如一行代码),能极大简化代码结构。

4.3.1 Lambda 槽函数的基本语法
代码语言:javascript
复制
QObject::connect(发送者, &发送者类::信号名, [捕获列表](参数列表) {
    // 槽函数逻辑
});
4.3.2 捕获列表的作用(Lambda 核心)

捕获列表用于指定 Lambda 表达式可以访问的外部变量,常用选项:

  • []不捕获任何外部变量(Lambda 内部无法访问外部变量)。
  • [=]值捕获(拷贝外部变量到 Lambda 内部,只读,不能修改)。
  • [&]引用捕获(引用外部变量,可修改,需确保变量生命周期)。
  • [this]:捕获当前对象的this指针(可访问当前类的成员变量和成员函数)。
  • [a, &b]混合捕获(a 按值捕获,b 按引用捕获)。
4.3.3 示例1:Lambda表达式的使用
4.3.4 示例2:以[=]方式传递参数,外部的所有变量在Lambda表达式中都可以被使用
4.3.5 示例3:以[a]方式传递,在Lambda表达式中只能够使用传递进来的a
4.3.6 Lambda 槽函数的使用场景与注意事项

  • 适用场景:槽逻辑简单(1-5 行代码)、无需复用的场景(如临时响应信号)。
  • 注意事项
    1. 变量生命周期:如果使用引用捕获([&][&var]),需确保 Lambda 执行时,被引用的变量仍然存在(如局部变量不能被引用捕获后在异步场景使用)。
    2. 线程安全:如果 Lambda 槽函数在其他线程执行(如QueuedConnection),避免访问非线程安全的变量(如 UI 组件)。
    3. 避免过度使用:复杂的槽逻辑(多行为、需复用)应单独声明槽函数,提高代码可读性和可维护性。

五、信号与槽的优缺点:理性选型,扬长避短

Qt 信号与槽机制虽然强大,但并非万能。了解其优缺点,能帮助我们在合适的场景选择合适的通信方式。

5.1 信号与槽的优点

  1. 松耦合设计:发送者与接收者完全解耦,无需知道对方的存在。例如:按钮不需要知道点击后会触发哪个窗口的关闭,窗口也不需要知道自己被哪个按钮控制。这种解耦让代码模块化程度更高,便于维护和扩展。
  2. 灵活的连接方式:支持一对多(一个信号连接多个槽)、多对一(多个信号连接一个槽)、跨线程连接等复杂场景,满足多样化的通信需求。
  3. 类型安全(Qt5):基于函数指针的连接方式在编译时检查信号与槽的匹配性,错误能及时发现,避免运行时隐藏问题。
  4. 支持 Lambda 表达式:简化简单槽逻辑的代码,无需额外声明槽函数,提高开发效率。
  5. 与 Qt 元对象系统深度集成:支持信号槽的动态连接 / 断开、信号参数的序列化(跨线程传递)等高级特性,无缝衔接 Qt 的其他功能(如属性系统、事件系统)。

5.2 信号与槽的缺点

  1. 一定的性能开销:相比直接函数调用,信号槽的调用存在额外开销(如元对象查找、参数拷贝、事件队列调度等)。虽然在大多数场景下这种开销可以忽略,但在高频触发的场景(如每秒触发数千次的信号),可能会影响性能。
  2. 调试难度较高:当信号槽连接复杂时(如多个信号连接多个槽),排查 "为什么槽函数不执行" 或 "槽函数执行多次" 的问题需要花费更多时间(需检查连接是否正确、对象是否存活、线程是否匹配等)。
  3. 学习成本:对于 C++ 初学者,信号槽的语法(如Q_OBJECT宏、emit关键字、连接类型)和底层原理(元对象系统、MOC 编译)需要一定的学习成本。
  4. 不支持模板函数:信号和槽函数不能是模板函数(因为 MOC 无法处理模板的元对象信息)。
  5. 跨语言兼容性差:信号槽是 Qt 特有的机制,无法直接与其他语言(如 Python、Java)的代码进行通信(需通过中间层转换)。

5.3 信号与槽的替代方案(性能敏感场景)

如果你的项目存在高频触发的通信场景(如实时数据采集、游戏帧更新),可以考虑以下替代方案,以牺牲部分灵活性换取更高性能:

  1. 直接函数调用:如果发送者和接收者的耦合是可接受的,直接调用成员函数是性能最高的方式。
  2. 函数对象(Functor):使用 C++11 的std::functionstd::bind,实现类似信号槽的灵活绑定,但性能开销更低(无元对象系统参与)。
  3. 观察者模式(自定义实现):手动实现简单的观察者模式(Subject + Observer),减少 Qt 信号槽的额外开销。

示例:使用std::function替代信号槽(高频场景)

代码语言:javascript
复制
#include <functional>
#include <QDebug>

class HighFreqSender
{
public:
    // 用std::function定义"槽函数类型"
    using Callback = std::function<void(int)>;

    // 注册回调(类似connect)
    void registerCallback(Callback cb) {
        m_callback = cb;
    }

    // 触发回调(类似emit信号)
    void trigger(int value) {
        if (m_callback) {
            m_callback(value); // 直接调用,性能接近普通函数
        }
    }

private:
    Callback m_callback;
};

// 使用示例
int main()
{
    HighFreqSender sender;

    // 注册回调(类似Lambda槽)
    sender.registerCallback([](int value) {
        qDebug() << "接收值:" << value;
    });

    // 高频触发(性能优于信号槽)
    for (int i = 0; i < 1000000; ++i) {
        sender.trigger(i);
    }

    return 0;
}

总结

信号与槽机制的学习需要结合大量实践,建议大家在实际项目中多尝试自定义信号槽、跨线程连接、Lambda 槽等用法,逐步掌握其核心精髓。相信通过本文的学习,大家已经具备了使用 Qt 信号槽解决复杂通信问题的能力,祝大家在 Qt 开发之路上越走越远!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 一、信号与槽的核心概念:Qt 的 "通信魔法"
  • 二、自定义信号和槽:从基础到进阶
    • 2.1 基本语法:无参数信号与槽的实现
    • 2.2 带参数的信号和槽:传递数据的核心方式
      • 2.2.1 带参数信号槽的关键规则
    • 2.3 自定义信号槽的常见问题排查
  • 三、信号与槽的连接方式:灵活控制通信行为
    • 3.1 连接类型的枚举定义(Qt::ConnectionType)
    • 3.2 各种连接类型的详细解析
      • 3.2.1 AutoConnection(默认连接)
      • 3.2.2 DirectConnection(直接连接)
      • 3.2.3 QueuedConnection(队列连接)
      • 3.2.4 BlockingQueuedConnection(阻塞队列连接)
      • 3.2.5 UniqueConnection(唯一连接)
    • 3.3 连接方式的实战选型建议
  • 四、信号与槽的高级用法:断开连接、Qt4 兼容与 Lambda 槽
    • 4.1 信号与槽的断开连接(disconnect)
      • 4.1.1 disconnect 的四种用法
      • 4.1.2 断开连接的注意事项
    • 4.2 Qt4 版本信号与槽的连接方式(兼容旧代码)
      • 4.2.1 Qt4 连接语法格式
      • 4.2.2 示例(Qt4 语法)
      • 4.2.3 Qt4 与 Qt5 连接方式的对比
      • 4.2.4 为什么不推荐 Qt4 语法?
    • 4.3 使用 Lambda 表达式定义槽函数(Qt5+)
      • 4.3.1 Lambda 槽函数的基本语法
      • 4.3.2 捕获列表的作用(Lambda 核心)
      • 4.3.3 示例1:Lambda表达式的使用
      • 4.3.4 示例2:以[=]方式传递参数,外部的所有变量在Lambda表达式中都可以被使用
      • 4.3.5 示例3:以[a]方式传递,在Lambda表达式中只能够使用传递进来的a
      • 4.3.6 Lambda 槽函数的使用场景与注意事项
  • 五、信号与槽的优缺点:理性选型,扬长避短
    • 5.1 信号与槽的优点
    • 5.2 信号与槽的缺点
    • 5.3 信号与槽的替代方案(性能敏感场景)
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档