深入理解 Qt:从原理到实战的全景指南

跨越 30 年的跨平台 GUI 框架王者,从架构原理到生产踩坑,一篇讲透


目录

  1. 发展历程
  2. 架构原理
  3. [元对象系统与 MOC](#元对象系统与 MOC)
  4. 信号与槽机制
  5. 事件循环
  6. 核心模块概览
  7. 应用场景
  8. [实战 Demo](#实战 Demo)
  9. 踩坑实录
  10. [Qt 6 新特性与迁移](#Qt 6 新特性与迁移)

01 发展历程:30 年的跨平台之路

Qt 的历史几乎与 Linux 桌面一样悠久。从 1995 年首个版本到如今 Qt 6,它经历了协议之争、公司易主、架构重构,却始终是 C++ GUI 开发的事实标准。

年份 里程碑 说明
1995 Qt 1.0 Haavard Nord 和 Eirik Chambe-Eng 创建 Qt,提供跨平台 GUI 工具包。名字源于 "Cute" 的谐音,也因字母 Q 在 Emacs 中看起来很独特
1996 KDE 诞生 Matthias Ettrich 基于 Qt 创建 KDE 桌面环境,Qt 的生态由此起飞,但也因 FreeQt 协议引发 GPL 阵营争议
1999 Qt 2.0 / Open Source Trolltech 将 Qt 开源协议切换为 QPL,后又推出 GPL 版本,彻底化解了自由软件社区的反对
2001 Qt 3.0 引入 Qt Designer 可视化设计器、完善 Unicode 支持和国际化、QSettings 等基础设施。同一套代码真正可运行于 Windows、Linux、macOS
2005 Qt 4.0 🔥 重大架构重构:将模块拆分为 QtCore、QtGui、QtNetwork 等;引入 QPainter 高质量渲染引擎;引入新的信号槽语法。这是 Qt 最为经典的版本
2008 Nokia 收购 Nokia 以 1.53 亿美元收购 Trolltech,Qt 开始在移动领域(Symbian/MeeGo)发力。后因 Nokia 战略转向 Windows Phone,Qt 的前途一度不明
2012 Qt 5.0 🔥 全面拥抱 QML/Qt Quick 声明式 UI,场景图(Scene Graph)替代 QPainter,Qt 从桌面走向移动端与嵌入式。Digia 从 Nokia 接手 Qt
2014 Qt Company 成立 Digia 将 Qt 业务分拆为独立公司 The Qt Company,同时在赫尔辛基交易所上市
2020 Qt 6.0 🔥 基于 C++17 重新构建,引入 RHI(Rendering Hardware Interface)统一图形 API;QML 类型系统全面强类型化;移除大量已弃用 API
2023--2026 Qt 6.x 持续演进 Qt 6.5+ 引入 Qt Quick 3D 物理、Wayland 合成器支持、Android/iOS 原生集成大幅增强,CMake 构建体系完全成熟,Qt for WebAssembly 日趋稳定

02 架构原理:分层设计的艺术

Qt 采用经典的分层架构,在应用程序与底层操作系统之间插入 Qt 框架层,实现 "Write Once, Compile Everywhere" 的跨平台能力。

复制代码
┌─────────────────────────────────────────────────┐
│              你的应用程序                          │
├─────────────────────────────────────────────────┤
│              Qt 框架层                            │
│  QtCore | QtGui | QtWidgets | QtNetwork | QtQml │
│  QtQuick | Qt3D | QtSQL | ...                   │
├─────────────────────────────────────────────────┤
│              Qt 平台抽象层 (QPA)                   │
│  Windows(QWindows) | macOS(QCocoa)              │
│  Linux(XCB/Wayland) | Android | iOS | WASM      │
├─────────────────────────────────────────────────┤
│              操作系统 / 硬件                        │
│  Win32 | Cocoa | X11 | Linux FB                 │
│  OpenGL | Vulkan | Metal | D3D                   │
└─────────────────────────────────────────────────┘

QPA:跨平台的秘密武器

Qt Platform Abstraction (QPA) 是 Qt 5 引入的平台抽象层接口。每个操作系统有自己的 QPA 插件实现(如 QWindows、QCocoa),Qt 上层代码只需调用统一 API,由 QPA 负责翻译为平台原生调用。这让 Qt 在新增平台时只需实现一个 QPA 插件即可,而非修改整个框架。

RHI:统一图形 API

Qt 6 引入 Rendering Hardware Interface (RHI),在应用层与底层图形 API 之间再加一层抽象。RHI 自动将 Qt Quick 的渲染指令翻译为 Vulkan、Metal、Direct3D 或 OpenGL,开发者无需关心底层差异。这是 Qt 6 在图形能力上的最大飞跃。


03 元对象系统与 MOC

Qt 的元对象系统 (Meta-Object System) 是整个框架最核心的基石。它为 C++ 增加了运行时类型信息(RTTI)、动态属性系统和信号槽能力------而这些,C++ 标准本身并不原生支持。

三大支柱

  • QObject 基类 --- 所有需要元对象能力的类必须继承 QObject
  • Q_OBJECT 宏 --- 在类声明中插入,声明元对象相关函数
  • MOC 编译器 --- 预处理头文件,生成含元信息的 C++ 代码

MOC 工作流程

MOC (Meta-Object Compiler) 是 Qt 的代码生成器。它扫描含 Q_OBJECT 宏的头文件,生成一个 moc_*.cpp 文件,其中包含:

  • 类的元对象静态数据(staticMetaObject
  • 信号函数体
  • qt_metacall() --- 按索引调用方法的分发器
  • 属性系统的读写函数
cpp 复制代码
// 你写的代码
class MyWidget : public QWidget {
    Q_OBJECT
signals:
    void valueChanged(int value);
public slots:
    void setValue(int v);
};

// MOC 生成的代码(简化)
const QMetaObject MyWidget::staticMetaObject = {
    nullptr,
    &QWidget::staticMetaObject,
    qt_meta_stringdata_MyWidget.data,
    qt_meta_data_MyWidget,
    qt_static_metacall,
    nullptr,
    nullptr
};

// 信号函数体由 MOC 生成,而非你手写
void MyWidget::valueChanged(int _t1) {
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

⚠️ 关于 MOC 的争议

MOC 是 Qt 最具争议的设计。它打破了 C++ 标准的编译模型,要求额外的预处理步骤。许多 C++ 纯粹主义者认为这 "不纯粹"。但 MOC 带来的收益是巨大的------它让 Qt 实现了标准 C++ 无法完成的动态元编程能力,且零运行时反射开销(所有元信息在编译期生成)。


04 信号与槽机制

信号与槽 (Signals & Slots) 是 Qt 最具标志性的设计模式,它实现了一种类型安全的观察者模式,让对象之间的通信变得松耦合且类型安全。

通信流程

复制代码
Sender 对象         Signal           Receiver 对象       Slot
[QPushButton] → emit clicked() → [clicked(bool)] → connect() → [MainWindow] → [onButtonClicked()]

两种连接语法

cpp 复制代码
// Qt 5+ 推荐的函数指针语法(编译期类型检查)
connect(button, &QPushButton::clicked,
        this, &MainWindow::onButtonClicked);

// Qt 4 风格的字符串语法(运行时查找,不推荐)
connect(button, SIGNAL(clicked(bool)),
        this, SLOT(onButtonClicked(bool)));

// Lambda 表达式(Qt 5+,灵活但需注意生命周期)
connect(button, &QPushButton::clicked, this, [this](bool checked) {
    qDebug() << "Clicked:" << checked;
});

// Qt 5.15+/6 的模板化 connect(支持 functor)
connect(timer, &QTimer::timeout, [this] {
    updateStatusBar();
});

连接类型

类型 行为 适用场景
Qt::AutoConnection 同线程直连,跨线程队列(默认) 99% 场景,不需要显式指定
Qt::DirectConnection 信号发出时立即在同线程调用槽 极低延迟需求,但需保证线程安全
Qt::QueuedConnection 槽在接收者事件循环中异步执行 跨线程通信的标准方式
Qt::BlockingQueuedConnection 同 Queued,但发送者会阻塞等待 需等待跨线程处理结果的场景

💡 最佳实践 :始终优先使用函数指针语法 + connect,这样编译器能在编译期检查信号/槽签名是否匹配,避免运行时才因参数不匹配而出错。


05 事件循环:Qt 的心脏

Qt 的一切交互------鼠标点击、定时器触发、网络响应、定时刷新------都由事件循环驱动。QCoreApplication::exec() 启动主事件循环,不断从事件队列中取出事件并分发。

事件处理流程

复制代码
1. 操作系统产生事件(鼠标/键盘/Timer/Socket)
                    ↓
2. QPA 将平台事件封装为 QEvent
                    ↓
3. QCoreApplication::notify() 分发事件
                    ↓
4. QWidget::event() 按类型路由到具体 handler
                    ↓
5. 你重写的 paintEvent() / mousePressEvent() / ...

事件 vs 信号

维度 事件 (QEvent) 信号 (Signal)
来源 系统 / Qt 框架产生 对象主动 emit
处理方式 事件循环队列 + 分发 直接函数调用
能否拦截 可以(installEventFilter) 不可以(已连接即触发)
典型场景 鼠标、键盘、绘制、定时器 业务逻辑通知

事件过滤器示例

cpp 复制代码
// 事件过滤器:在事件到达目标 widget 之前拦截
bool MyWidget::eventFilter(QObject *watched, QEvent *event) {
    if (watched == m_lineEdit && event->type() == QEvent::KeyPress) {
        auto *keyEvent = static_cast<QKeyEvent*>(event);
        if (keyEvent->key() == Qt::Key_Return) {
            handleReturnPressed();
            return true;  // 事件已处理,不再传递
        }
    }
    return QWidget::eventFilter(watched, event);
}

// 安装过滤器
m_lineEdit->installEventFilter(this);

06 核心模块概览

Qt 不是单一库,而是一个模块化的框架生态系统。按功能划分为 Essentials(核心必备)和 Add-Ons(可选扩展)。

QtCore

非 GUI 核心基础设施:元对象系统、事件循环、线程(QThread/QThreadPool)、容器(QVector/QHash)、文件 I/O、JSON、状态机、插件框架。

QtGui

GUI 基础:窗口系统集成(QWindow)、2D 图形(QPainter/QImage/QPixmap)、字体、颜色、光标、OpenGL/Vulkan 集成、RHI 抽象。

QtWidgets

经典桌面控件集:按钮、输入框、表格、树形、菜单、工具栏、对话框。C++ 原生 Widget 开发的主力模块。

QtQuick / QML

声明式 UI 框架:QML 语言描述 UI,JavaScript 处理逻辑,C++ 提供后端。流畅动画、触摸友好、适合现代 UI。

QtNetwork

TCP/UDP Socket(QTcpSocket/QUdpSocket)、HTTP(QNetworkAccessManager)、SSL/TLS、DNS、Bearer 管理。

QtSQL

数据库访问层:支持 SQLite、MySQL、PostgreSQL、ODBC。QSqlDatabase/QSqlQuery 提供统一的 SQL 操作接口。

QtQuick3D

3D 场景渲染:基于 QML 声明式描述 3D 场景,支持导入 glTF/FBX 模型,PBR 材质,后处理特效。

QtTest

单元测试框架:QCOMPARE/QVERIFY 宏、数据驱动测试、Benchmark、GUI 事件模拟。


07 应用场景:Qt 无处不在

从桌面应用到汽车仪表盘,从医疗器械到卫星地面站,Qt 的身影遍布各行各业。

🖥️ 桌面应用开发

Qt 最经典的战场。IDE(Qt Creator)、办公软件(WPS)、设计工具、通信软件均使用 Qt 构建。Qt Widgets 提供原生外观,QML 提供现代体验。

代表:WPS Office、VLC、Telegram、OBS Studio

🏭 嵌入式 & 工业

Qt for MCU 可在无 OS 的微控制器上运行;Qt for Device Creation 在 Linux 嵌入式设备上提供完整 UI 框架。工业 HMI、医疗设备、POS 终端是核心场景。

代表:医疗影像设备、工业 PLC 面板、智能家居中枢

🚗 汽车 IVI 系统

Qt 是车载信息娱乐系统 (IVI) 的主流框架。GENIVI/COVESA 联盟推荐 Qt 构建 HMI,大量汽车厂商的仪表盘、中控屏基于 Qt 开发。

代表:Mercedes MBUX、Volvo Sensus、FCA Uconnect

📱 移动端应用

Qt 可编译到 Android/iOS,一套代码多端运行。虽然市场份额不如原生和 Flutter,但在 IoT 配套 App、工业移动工具等垂直领域仍有优势。

适用:IoT 配套 App、工业巡检工具、跨平台工具类 App

🛰️ 航天 & 国防

卫星地面站控制台、雷达界面、飞行模拟器......Qt 的跨平台能力、信号槽的松耦合设计、以及 LGPL/商业双授权使其成为军工领域的常客。

代表:ESA 卫星控制台、军事指挥系统

🌐 WebAssembly

Qt for WebAssembly 可将 Qt 应用编译为 WASM,直接在浏览器中运行。无需安装即可展示桌面级应用,适合在线 Demo、远程工具等场景。

适用:在线工具演示、远程运维界面


08 实战 Demo:从零到一

Demo 1:最小 QtWidgets 应用

这是每个 Qt 开发者的 "Hello World"------一个带按钮的窗口,点击按钮关闭应用。

cpp 复制代码
// main.cpp
#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QPushButton button("Hello Qt!");
    button.resize(200, 60);
    button.show();

    QObject::connect(&button, &QPushButton::clicked,
                     &app, &QApplication::quit);

    return app.exec();
}

Demo 2:自定义 Widget 与信号槽

一个温度转换器:输入摄氏度,实时显示华氏度。

cpp 复制代码
// tempconverter.h
#include <QWidget>
#include <QLineEdit>
#include <QLabel>
#include <QHBoxLayout>

class TempConverter : public QWidget {
    Q_OBJECT
public:
    explicit TempConverter(QWidget *parent = nullptr)
        : QWidget(parent) {
        auto *layout = new QHBoxLayout(this);
        m_input = new QLineEdit("0");
        m_result = new QLabel("32 °F");

        layout->addWidget(m_input);
        layout->addWidget(m_result);

        connect(m_input, &QLineEdit::textChanged,
                this, &TempConverter::convert);
    }

private slots:
    void convert() {
        bool ok;
        double celsius = m_input->text().toDouble(&ok);
        if (!ok) {
            m_result->setText("Invalid input");
            return;
        }
        double fahrenheit = celsius * 9.0 / 5.0 + 32.0;
        m_result->setText(QString::number(fahrenheit, 'f', 1) + " °F");
    }

private:
    QLineEdit *m_input;
    QLabel   *m_result;
};

Demo 3:QML 声明式 UI

同样的温度转换器,用 QML 实现------代码量更少,界面更现代。

qml 复制代码
// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

ApplicationWindow {
    visible: true
    width: 400; height: 200
    title: "Temp Converter"

    RowLayout {
        anchors.centerIn: parent
        spacing: 16

        TextField {
            id: celsiusInput
            text: "0"
            placeholderText: "Celsius"
            onTextChanged: {
                var val = parseFloat(text)
                fahrenheitLabel.text = isNaN(val)
                    ? "Invalid"
                    : (val * 9.0 / 5.0 + 32.0).toFixed(1) + " °F"
            }
        }

        Label {
            id: fahrenheitLabel
            text: "32.0 °F"
            font.pixelSize: 18
        }
    }
}

Demo 4:多线程 Worker

在后台线程执行耗时计算,通过信号通知 UI 更新------这是 Qt 多线程的标准范式。

cpp 复制代码
// worker.h
class Worker : public QObject {
    Q_OBJECT
public slots:
    void doWork(int param) {
        int result = heavyComputation(param);  // 耗时操作
        emit resultReady(result);
    }
signals:
    void resultReady(int result);
};

// main.cpp(设置线程)
QThread *workerThread = new QThread;
Worker  *worker      = new Worker;

worker->moveToThread(workerThread);

// 启动线程后自动执行 doWork
connect(workerThread, &QThread::started,
        worker, &Worker::doWork);

// 结果回传主线程
connect(worker, &Worker::resultReady,
        this, &MainWindow::handleResult);

// 线程结束时清理
connect(workerThread, &QThread::finished,
        worker, &QObject::deleteLater);

workerThread->start();

Demo 5:CMake 构建配置 (Qt 6)

Qt 6 已全面拥抱 CMake。这是最小项目的 CMakeLists.txt。

cmake 复制代码
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(TempConverter LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)

add_executable(TempConverter
    main.cpp
    tempconverter.h
    tempconverter.cpp
)

target_link_libraries(TempConverter
    PRIVATE Qt6::Widgets
)

09 踩坑实录:血泪教训

以下每一个坑,都曾是无数 Qt 开发者的深夜噩梦。记住它们,别再踩。

❌ 坑 1:忘记 Q_OBJECT 宏

症状 :信号槽不工作、qobject_cast 返回 nullptr、元属性系统失效。编译不出错,但运行时行为诡异。

原因 :MOC 只处理含 Q_OBJECT 宏的类。没有它,就不会生成 moc_*.cpp,元对象系统形同虚设。

解法 :只要你的类有信号/槽/属性,就必须加 Q_OBJECT。养成习惯:继承 QObject 的类一律加上。


❌ 坑 2:在非主线程操作 UI

症状:随机崩溃、界面闪烁、控件不更新。Debug 模式下控制台会打印 "QObject::setParent: Cannot set parent, new parent is in a different thread"。

原因:Qt 的 Widget 系统不是线程安全的。所有 UI 操作必须在主线程(GUI 线程)执行。

解法 :用 QMetaObject::invokeMethod() 或信号槽(Qt::QueuedConnection)将操作封送回主线程:

cpp 复制代码
// 错误:在子线程直接更新 UI
void Worker::onProgress(int pct) {
    m_progressBar->setValue(pct);  // 💥 危险!
}

// 正确:通过信号槽跨线程通信
// Worker 在子线程 emit progress(int)
// UI 在主线程通过 connect 接收
connect(worker, &Worker::progress,
        progressBar, &QProgressBar::setValue);
// AutoConnection 会自动使用 QueuedConnection

// 或使用 invokeMethod
QMetaObject::invokeMethod(progressBar, [pct] {
    progressBar->setValue(pct);
}, Qt::QueuedConnection);

❌ 坑 3:QObject 父子关系与内存泄漏

症状:对象未被释放,内存持续增长。或者更糟------double free 崩溃。

原因 :Qt 的对象树 (Object Tree) 机制会自动 delete 子对象。如果你手动 delete 了一个已被父对象管理的子对象,父对象析构时会再次 delete,导致 double free。

解法 :遵循 Qt 的内存管理哲学------尽量让父对象管理子对象生命周期,不要手动 delete 由父对象管理的子对象。如果必须手动删除,使用 deleteLater()


❌ 坑 4:信号槽参数类型不匹配(SIGNAL/SLOT 宏语法)

症状:connect 返回 true 但槽永远不被调用。控制台可能有警告 "No such signal" 或 "No such slot"。

原因 :字符串语法 SIGNAL(clicked()) / SLOT(onClick()) 在编译期不做类型检查,拼写错误或签名不匹配只能在运行时发现。

解法 :始终使用函数指针语法 &Class::signal。如果签名不完全匹配,用 lambda 适配。


❌ 坑 5:QThread 的 "继承陷阱"

症状 :以为重写 QThread::run() 中的代码在新线程执行,结果 QThread::start() 后发现槽函数仍在主线程。

原因 :QThread 对象本身属于创建它的线程。只有 run() 内部代码在新线程执行。直接在 QThread 子类中定义的槽仍在主线程执行(因为 QThread 对象的 thread affinity 是主线程)。

解法 :使用 Worker-Thread 模式 ------创建独立的 Worker 对象,moveToThread() 移入 QThread,而非继承 QThread。

cpp 复制代码
// ❌ 反模式:继承 QThread + 在类中定义槽
class MyThread : public QThread {
    Q_OBJECT
private slots:
    void onTimeout() {
        // 这个槽在主线程执行!不是新线程!
        qDebug() << thread();  // 输出主线程
    }
};

// ✅ 正确:Worker 模式
class Worker : public QObject {  // 注意:继承 QObject,不是 QThread
    Q_OBJECT
public slots:
    void onTimeout() {
        // moveToThread 后,这个槽在新线程执行
        qDebug() << thread();  // 输出工作线程
    }
};

❌ 坑 6:CMake 中忘记自动处理 MOC

症状:链接错误 "undefined reference to vtable"。

原因 :Qt 6 的 CMake 集成会自动处理 MOC,但前提是头文件被正确加入 add_executable 的源列表。如果只加入 .cpp 而漏了 .h,MOC 不会扫描该头文件。

解法 :确保所有含 Q_OBJECT.h 文件都加入 add_executable 源列表。


❌ 坑 7:QPainter 在 paintEvent 之外使用

症状:控制台警告 "QPainter::begin: Widget painting can only begin as a result of a paintEvent",绘制无效。

原因 :Qt 的绘制系统要求 QPainter 必须在 paintEvent() 回调中使用。在别处创建的 QPainter 无法正确绑定到 Widget 的绘制上下文。

解法 :不要在 paintEvent() 之外直接绘制 Widget。如需触发重绘,调用 update(),让 Qt 在下一个 paintEvent() 中统一处理。


❌ 坑 8:信号槽连接导致内存泄漏(Lambda 忘记设置 context)

症状:对象已销毁但 lambda 仍持有悬空指针,导致崩溃或逻辑错误。

原因connect(sender, &Sender::signal, [this]() { ... }) 没有 context 对象,连接不会在 receiver 销毁时自动断开。

解法 :始终在 connect 中指定 context 对象:connect(sender, &Sender::signal, this, [this]() { ... })。这样当 this 销毁时,连接自动断开。


10 Qt 6 新特性与迁移指南

核心变化

  • C++17 最低要求 --- 大量使用 std::optionalstd::variant、结构化绑定等新特性
  • RHI 统一图形后端 --- 不再需要分别处理 OpenGL / Vulkan / Metal / D3D
  • QML 强类型化 --- 属性必须有类型声明,消除大量运行时类型错误
  • CMake 为主构建系统 --- qmake 仍可用但不再是首选
  • 大量 API 清理 --- 移除 Qt 5 中标记为 deprecated 的旧 API
  • QString 转为 UTF-16 内部编码 --- Qt 6 明确了 QStringView / qsizetype 等类型

Qt 5 → Qt 6 迁移检查清单

检查项 说明
枚举类型 Qt 6 使用 Q_ENUM 代替裸 enum,需检查是否用到了旧式枚举
QRegEx → QRegularExpression QRegEx 在 Qt 6 中已移除,必须替换
QVector → QList Qt 6 中 QList 和 QVector 已统一为 QList
qMakePair → std::make_pair Qt 容器辅助函数大量替换为 STL 等价物
QGraphicsEffect Qt 6.0 移除,6.5 后部分恢复,需确认替代方案
构建系统 从 qmake 迁移到 CMake(官方提供 pro2cmake 工具)
QML 类型声明 所有 QML 属性必须有 property type name 显式类型

💡 迁移建议 :不要急于一步到位。Qt 官方提供 qt5compat 模块,可以在 Qt 6 中临时使用 Qt 5 的旧 API。先让项目编译通过,再逐步替换为 Qt 6 新 API。


Qt 技术深度解析 · 2026 年 6 月 · 基于公开文档与实践经验整理

参考资料:Qt 6 官方文档、KDAB 博客、Qt Wiki、ICS 白皮书

相关推荐
放下华子我只抽RuiKe51 小时前
FastAPI 全栈后端(四):认证与授权
开发语言·前端·javascript·python·深度学习·react.js·fastapi
我是唐青枫2 小时前
Java Spring Data JPA 实战指南:Repository 查询、分页与实体映射
java·开发语言
张忠琳2 小时前
【Go 1.26.4】(Part 2) Go 1.26.4 超深度分析 — Runtime GMP 调度器 (proc.go + runtime2.go)
开发语言·golang
阿坤带你走近大数据2 小时前
java中泛型不能用基础数据类型
java·开发语言
weixin_307779132 小时前
从脚本执行到智能体协作:AI辅助测试能力的范式重构
运维·开发语言·人工智能·算法·测试用例
云絮.3 小时前
增删改查操作
java·开发语言
themingyi3 小时前
Abaqus2024安装python包pandas
开发语言·python·pandas
阿正的梦工坊3 小时前
【Rust】19-FFI、ABI 与跨语言边界设计
开发语言·后端·rust
殇淋狱陌3 小时前
Python列表知识思维导图
开发语言·python·学习