当前位置: 首页 > news >正文

【QT】UI 开发全攻略:打造专业级跨平台界面

在这里插入图片描述

个人主页:Guiat
归属专栏:QT

在这里插入图片描述

文章目录

  • 1. Qt UI开发基石:从零开始构建界面
    • 1.1 为什么Qt是UI开发首选
    • 1.2 Qt Designer可视化设计
    • 1.3 UI文件与代码的协作
  • 2. 布局管理系统:自适应界面的秘密武器
    • 2.1 手动布局 vs 自动布局
    • 2.2 四大基础布局详解
    • 2.3 嵌套布局实战技巧
  • 3. 样式表QSS:打造个性化界面
    • 3.1 QSS基础语法速成
    • 3.2 常用样式属性大全
    • 3.3 高级QSS技巧
  • 4. 信号与槽:UI交互的核心机制
    • 4.1 基础信号槽连接
    • 4.2 自定义信号与槽
    • 4.3 跨线程信号处理
  • 5. 对话框设计艺术
    • 5.1 模态与非模态对话框
    • 5.2 标准对话框应用
    • 5.3 自定义高级对话框
  • 6. 主窗口框架深度解析
    • 6.1 QMainWindow核心结构
    • 6.2 菜单栏与工具栏实战
    • 6.3 状态栏与Dock窗口
  • 7. 国际化与多语言支持
    • 7.1 多语言实现原理
    • 7.2 Qt Linguist使用流程
    • 7.3 动态语言切换
  • 8. 动画与图形特效
    • 8.1 属性动画基础
    • 8.2 动画组高级应用
    • 8.3 图形特效框架
  • 9. 模型视图编程
    • 9.1 MVC模式解析
    • 9.2 自定义模型实现
    • 9.3 代理Delegate高级应用
  • 10. 高级UI技术集锦
    • 10.1 OpenGL集成
    • 10.2 QML混合开发
    • 10.3 跨平台适配技巧

正文

10大核心模块深度解析,200+实用代码示例,助你从UI小白进阶界面开发大师!

1. Qt UI开发基石:从零开始构建界面

1.1 为什么Qt是UI开发首选

跨平台优势:一次编写,Windows/macOS/Linux/Android/iOS全平台运行
强大工具链:Qt Designer + Qt Creator + QMake/CMake完整生态
企业级应用:Autodesk Maya、VirtualBox、WPS Office等知名应用案例

【举例】:使用Qt开发医疗影像系统界面:

医学影像
Qt界面框架
多平台支持
Windows工作站
Linux服务器
Android平板

1.2 Qt Designer可视化设计

核心功能:拖拽式布局、实时预览、样式编辑、信号槽连接

【code】创建主窗口并加载UI文件:

#include <QApplication>
#include <QMainWindow>
#include "ui_mainwindow.h" // 设计师生成的UI头文件int main(int argc, char *argv[]) {QApplication app(argc, argv);QMainWindow window;Ui::MainWindow ui;ui.setupUi(&window); // 加载UI设计window.show();return app.exec();
}

高效工作流

  1. 在Qt Designer中设计界面(.ui文件)
  2. 使用uic工具生成UI头文件
  3. 在代码中集成并添加业务逻辑

1.3 UI文件与代码的协作

UI文件结构解析

<ui version="4.0"><class>MainWindow</class><widget class="QMainWindow" name="MainWindow"><property name="geometry"><rect><x>0</x><y>0</y><width>800</width><height>600</height></rect></property><widget class="QWidget" name="centralwidget"><!-- 子控件定义 --></widget></widget><resources/> <!-- 资源文件 --><connections/> <!-- 信号槽连接 -->
</ui>

动态修改UI技巧

// 在运行时添加控件
void MainWindow::addNewButton() {QPushButton *newBtn = new QPushButton("动态按钮", ui->centralWidget);newBtn->setGeometry(50, 50, 100, 30);connect(newBtn, &QPushButton::clicked, [](){qDebug() << "动态按钮被点击!";});
}

2. 布局管理系统:自适应界面的秘密武器

2.1 手动布局 vs 自动布局

手动布局痛点

  • 位置固定,无法适应不同分辨率
  • 修改麻烦,牵一发而动全身
  • 多语言支持困难

2.2 四大基础布局详解

QHBoxLayout水平布局

QWidget *container = new QWidget;
QHBoxLayout *hLayout = new QHBoxLayout(container);hLayout->addWidget(new QPushButton("左"));
hLayout->addWidget(new QPushButton("中"));
hLayout->addWidget(new QPushButton("右"));// 添加弹性空间
hLayout->addStretch();
hLayout->addWidget(new QPushButton("最右边"));

QVBoxLayout垂直布局

QVBoxLayout *vLayout = new QVBoxLayout;
vLayout->addWidget(new QLabel("标题"));
vLayout->addWidget(new QLineEdit);
vLayout->addWidget(new QTextEdit);// 设置控件间距
vLayout->setSpacing(10);

QGridLayout网格布局

QGridLayout *grid = new QGridLayout;// 添加控件到网格
grid->addWidget(new QLabel("用户名:"), 0, 0);
grid->addWidget(new QLineEdit, 0, 1);
grid->addWidget(new QLabel("密码:"), 1, 0);
grid->addWidget(new QLineEdit, 1, 1);// 跨行跨列
grid->addWidget(new QTextEdit, 2, 0, 1, 2); // 占据1行2列

QFormLayout表单布局

QFormLayout *form = new QFormLayout;
form->addRow("姓名:", new QLineEdit);
form->addRow("年龄:", new QSpinBox);
form->addRow("简介:", new QTextEdit);// 设置标签对齐方式
form->setLabelAlignment(Qt::AlignRight);

2.3 嵌套布局实战技巧

复杂界面布局策略

主布局
顶部工具栏
中间区域
左侧导航
右侧内容
底部状态栏

代码实现

// 创建主窗口布局
QVBoxLayout *mainLayout = new QVBoxLayout;// 1. 顶部工具栏
QHBoxLayout *toolbar = new QHBoxLayout;
toolbar->addWidget(new QToolButton);
toolbar->addWidget(new QLineEdit("搜索..."));
mainLayout->addLayout(toolbar);// 2. 中间区域
QHBoxLayout *contentLayout = new QHBoxLayout;// 左侧导航
QVBoxLayout *navLayout = new QVBoxLayout;
navLayout->addWidget(new QListWidget);
contentLayout->addLayout(navLayout, 1); // 比例1// 右侧内容
QSplitter *splitter = new QSplitter(Qt::Horizontal);
splitter->addWidget(new QTreeWidget);
splitter->addWidget(new QTextEdit);
contentLayout->addWidget(splitter, 3); // 比例3mainLayout->addLayout(contentLayout, 1); // 中间区域占主要空间// 3. 底部状态栏
QHBoxLayout *statusLayout = new QHBoxLayout;
statusLayout->addWidget(new QLabel("就绪"));
statusLayout->addWidget(new QProgressBar);
mainLayout->addLayout(statusLayout);

3. 样式表QSS:打造个性化界面

3.1 QSS基础语法速成

核心语法结构

选择器 {属性: 值;子控件 {属性: 值;}
}

常用选择器类型

QSS选择器
+类选择器 QPushButton
+ID选择器 #submitBtn
+后代选择器 QDialog QLabel
+子元素选择器 QMenu > QItem
+伪状态选择器 QPushButton:hover

3.2 常用样式属性大全

文本样式

QLabel {color: #333; /* 文字颜色 */font-family: "微软雅黑"; /* 字体 */font-size: 14px; /* 字号 */font-weight: bold; /* 粗体 */
}

背景与边框

QPushButton {background-color: #4CAF50; /* 背景色 */border: 2px solid #388E3C; /* 边框 */border-radius: 5px; /* 圆角 */padding: 8px 16px; /* 内边距 */
}

悬停与按压效果

QPushButton:hover {background-color: #45a049; /* 悬停颜色 */
}QPushButton:pressed {background-color: #3d8b40; /* 按压颜色 */border-color: #2E7D32;
}

3.3 高级QSS技巧

样式继承与覆盖

// 全局样式
qApp->setStyleSheet("QPushButton { border-radius: 3px; }");// 特定按钮覆盖
submitBtn->setStyleSheet("background-color: #FF5722;");

动态样式切换

// 白天模式
void setDayTheme() {qApp->setStyleSheet("QWidget { background: white; color: black; }""QLineEdit { border: 1px solid #ccc; }");
}// 夜间模式
void setNightTheme() {qApp->setStyleSheet("QWidget { background: #2D2D30; color: #DCDCDC; }""QLineEdit { background: #1E1E1E; border: 1px solid #3F3F46; }");
}

资源文件使用

/* 使用资源路径 */
QPushButton#iconBtn {background-image: url(:/images/icon.png);background-repeat: no-repeat;background-position: center;
}

4. 信号与槽:UI交互的核心机制

4.1 基础信号槽连接

五种连接方式对比

信号
信号发送者
槽函数
Lambda
直接处理
自动连接
UI设计器

标准连接方式

// 按钮点击事件
connect(ui->btnOpen, &QPushButton::clicked, this, &MainWindow::openFile);// 文本变化事件
connect(ui->lineEdit, &QLineEdit::textChanged,[](const QString &text) {qDebug() << "当前文本:" << text;});

4.2 自定义信号与槽

创建自定义信号

class Document : public QObject {Q_OBJECT
public:void modifyContent() {// 修改内容后发出信号emit contentModified(true);}signals:void contentModified(bool changed);
};

带参数的自定义槽

class MainWindow : public QMainWindow {Q_OBJECT
public slots:void handleDocumentChange(bool changed) {ui->saveBtn->setEnabled(changed);setWindowTitle(changed ? "*文档" : "文档");}
};// 连接
connect(document, &Document::contentModified,mainWindow, &MainWindow::handleDocumentChange);

4.3 跨线程信号处理

线程安全通信

// 在工作线程中
void Worker::run() {while(!stopped) {// 处理任务...emit progressUpdated(percent);QThread::msleep(100);}
}// 在主线程中
connect(worker, &Worker::progressUpdated,ui->progressBar, &QProgressBar::setValue);// 使用QueuedConnection确保线程安全
connect(worker, &Worker::resultReady,this, &MainWindow::handleResult,Qt::QueuedConnection);

5. 对话框设计艺术

5.1 模态与非模态对话框

代码实现

// 模态对话框(阻塞操作)
void showModalDialog() {QDialog dialog(this);dialog.exec(); // 阻塞直到关闭qDebug() << "模态对话框已关闭";
}// 非模态对话框
void showModelessDialog() {QDialog *dialog = new QDialog(this);dialog->setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动删除dialog->show();qDebug() << "非模态对话框已显示";
}

5.2 标准对话框应用

文件对话框

// 打开单个文件
QString file = QFileDialog::getOpenFileName(this, "打开文件", QDir::homePath(),"图像文件 (*.png *.jpg);;所有文件 (*.*)"
);// 选择多个文件
QStringList files = QFileDialog::getOpenFileNames(this, "选择多个文件"
);// 保存文件
QString savePath = QFileDialog::getSaveFileName(this, "保存文件", "", "文本文件 (*.txt)"
);

颜色与字体选择

// 颜色选择
QColor color = QColorDialog::getColor(Qt::white, this);
if(color.isValid()) {ui->textEdit->setTextColor(color);
}// 字体选择
bool ok;
QFont font = QFontDialog::getFont(&ok, QFont("Arial", 12), this);
if(ok) {ui->textEdit->setFont(font);
}

5.3 自定义高级对话框

向导式对话框

QDialog wizard(this);
QVBoxLayout *layout = new QVBoxLayout(&wizard);// 步骤1
QWidget *page1 = new QWidget;
page1->setLayout(new QVBoxLayout);
page1->layout()->addWidget(new QLabel("步骤1:基本信息"));// 步骤2
QWidget *page2 = new QWidget;
// ...其他步骤QStackedWidget *stack = new QStackedWidget;
stack->addWidget(page1);
stack->addWidget(page2);QPushButton *backBtn = new QPushButton("上一步");
QPushButton *nextBtn = new QPushButton("下一步");layout->addWidget(stack);
layout->addWidget(backBtn);
layout->addWidget(nextBtn);// 切换逻辑
connect(nextBtn, &QPushButton::clicked, [stack](){stack->setCurrentIndex(stack->currentIndex() + 1);
});

6. 主窗口框架深度解析

6.1 QMainWindow核心结构

QMainWindow
+menuBar()
+toolBarArea()
+statusBar()
+centralWidget()
+addDockWidget()
QMenuBar
QToolBar
QStatusBar
QDockWidget

6.2 菜单栏与工具栏实战

动态创建菜单

// 创建菜单栏
QMenuBar *menuBar = new QMenuBar(this);
setMenuBar(menuBar);// 文件菜单
QMenu *fileMenu = menuBar->addMenu("文件");
fileMenu->addAction("新建", this, &MainWindow::newFile);
fileMenu->addAction("打开", this, &MainWindow::openFile);
fileMenu->addSeparator();
fileMenu->addAction("退出", qApp, &QApplication::quit);// 编辑菜单
QMenu *editMenu = menuBar->addMenu("编辑");
editMenu->addAction("复制", this, &MainWindow::copy);
editMenu->addAction("粘贴", this, &MainWindow::paste);// 创建工具栏
QToolBar *toolBar = addToolBar("主工具栏");
toolBar->addAction(QIcon(":/icons/new.png"), "新建", this, &MainWindow::newFile);
toolBar->addAction(QIcon(":/icons/open.png"), "打开");
toolBar->addSeparator();
toolBar->addAction(QIcon(":/icons/save.png"), "保存");

6.3 状态栏与Dock窗口

状态栏应用

// 获取状态栏
QStatusBar *statusBar = this->statusBar();// 添加永久组件
QLabel *positionLabel = new QLabel("行: 1 列: 1");
statusBar->addPermanentWidget(positionLabel);// 临时消息
statusBar->showMessage("就绪", 3000); // 显示3秒// 添加进度条
QProgressBar *progressBar = new QProgressBar;
progressBar->setMaximumWidth(200);
statusBar->addPermanentWidget(progressBar);
progressBar->setVisible(false); // 默认隐藏

Dock窗口系统

// 创建左侧Dock
QDockWidget *leftDock = new QDockWidget("导航面板", this);
leftDock->setWidget(new QTreeWidget);
addDockWidget(Qt::LeftDockWidgetArea, leftDock);// 创建右侧Dock
QDockWidget *rightDock = new QDockWidget("属性面板", this);
rightDock->setWidget(new QTableWidget);
addDockWidget(Qt::RightDockWidgetArea, rightDock);// 允许浮动和关闭
leftDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable |QDockWidget::DockWidgetClosable
);// 恢复布局
QSettings settings;
restoreState(settings.value("windowState").toByteArray());

7. 国际化与多语言支持

7.1 多语言实现原理

用户应用翻译引擎界面选择语言加载QM文件更新所有文本显示新语言用户应用翻译引擎界面

7.2 Qt Linguist使用流程

  1. 在代码中使用tr()标记可翻译文本
QString title = tr("Main Window");
QMenu *fileMenu = new QMenu(tr("File"));
  1. 生成TS文件
lupdate project.pro -ts app_zh_CN.ts
  1. 使用Qt Linguist翻译TS文件

  2. 生成QM二进制文件

lrelease app_zh_CN.ts -qm app_zh_CN.qm

7.3 动态语言切换

// 加载翻译文件
void loadTranslation(const QString &language) {QTranslator *translator = new QTranslator;if(translator->load("app_" + language + ".qm")) {qApp->installTranslator(translator);}
}// 切换语言槽函数
void MainWindow::switchLanguage() {if(sender() == ui->actionChinese) {loadTranslation("zh_CN");} else if(sender() == ui->actionEnglish) {loadTranslation("en");}// 重载UIui->retranslateUi(this);setWindowTitle(tr("Main Window"));
}

8. 动画与图形特效

8.1 属性动画基础

简单动画示例

// 按钮动画:移动+缩放
QPushButton *btn = new QPushButton("动画按钮", this);
btn->setGeometry(50, 50, 100, 40);// 位置动画
QPropertyAnimation *posAnim = new QPropertyAnimation(btn, "geometry");
posAnim->setDuration(1000);
posAnim->setStartValue(QRect(50, 50, 100, 40));
posAnim->setEndValue(QRect(200, 200, 100, 40));// 缩放动画
QPropertyAnimation *scaleAnim = new QPropertyAnimation(btn, "size");
scaleAnim->setDuration(800);
scaleAnim->setStartValue(QSize(100, 40));
scaleAnim->setEndValue(QSize(150, 60));// 并行执行
QParallelAnimationGroup *group = new QParallelAnimationGroup;
group->addAnimation(posAnim);
group->addAnimation(scaleAnim);
group->start();

8.2 动画组高级应用

序列动画组

// 创建三个动画
QPropertyAnimation *anim1 = createAnim(btn, "pos", QPoint(0,0), QPoint(100,0));
QPropertyAnimation *anim2 = createAnim(btn, "pos", QPoint(100,0), QPoint(100,100));
QPropertyAnimation *anim3 = createAnim(btn, "pos", QPoint(100,100), QPoint(0,100));// 创建序列
QSequentialAnimationGroup *seqGroup = new QSequentialAnimationGroup;
seqGroup->addAnimation(anim1);
seqGroup->addAnimation(anim2);
seqGroup->addAnimation(anim3);// 循环播放
seqGroup->setLoopCount(3);
seqGroup->start();

8.3 图形特效框架

阴影效果

QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect;
shadow->setBlurRadius(15); // 模糊半径
shadow->setOffset(5, 5);   // 偏移量
shadow->setColor(QColor(0, 0, 0, 160)); // 阴影颜色ui->cardWidget->setGraphicsEffect(shadow);

透明渐变效果

QGraphicsOpacityEffect *opacity = new QGraphicsOpacityEffect;
opacity->setOpacity(0.7); // 70%透明度ui->toolbar->setGraphicsEffect(opacity);// 动画改变透明度
QPropertyAnimation *fadeAnim = new QPropertyAnimation(opacity, "opacity");
fadeAnim->setDuration(1000);
fadeAnim->setStartValue(0.3);
fadeAnim->setEndValue(1.0);
fadeAnim->start();

9. 模型视图编程

9.1 MVC模式解析

数据变化
用户交互
更新数据
Model 数据模型
View 视图
Controller 控制器

9.2 自定义模型实现

文件系统模型示例

class FileSystemModel : public QAbstractListModel {
public:int rowCount(const QModelIndex &) const override {return files.count();}QVariant data(const QModelIndex &index, int role) const override {if(!index.isValid()) return QVariant();const FileInfo &file = files[index.row()];switch(role) {case Qt::DisplayRole: // 显示文本return file.name;case Qt::DecorationRole: // 图标return QIcon(file.isDir ? ":/icons/folder.png" : ":/icons/file.png");case Qt::ToolTipRole: // 提示return QString("大小: %1\n修改时间: %2").arg(file.size).arg(file.modified.toString());}return QVariant();}private:struct FileInfo {QString name;qint64 size;QDateTime modified;bool isDir;};QVector<FileInfo> files;
};

9.3 代理Delegate高级应用

进度条代理

class ProgressDelegate : public QStyledItemDelegate {
public:void paint(QPainter *painter, const QStyleOptionViewItem &option,const QModelIndex &index) const override {if(index.column() == 2) { // 进度列int progress = index.data().toInt();QStyleOptionProgressBar progressOption;progressOption.rect = option.rect.adjusted(2, 2, -2, -2);progressOption.minimum = 0;progressOption.maximum = 100;progressOption.progress = progress;progressOption.text = QString::number(progress) + "%";progressOption.textVisible = true;QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressOption, painter);} else {QStyledItemDelegate::paint(painter, option, index);}}
};// 使用代理
tableView->setItemDelegateForColumn(2, new ProgressDelegate);

10. 高级UI技术集锦

10.1 OpenGL集成

3D图形界面开发

// 创建OpenGL窗口
class GLWidget : public QOpenGLWidget {
protected:void initializeGL() override {initializeOpenGLFunctions();glClearColor(0.2f, 0.3f, 0.3f, 1.0f);}void paintGL() override {glClear(GL_COLOR_BUFFER_BIT);// 绘制三角形glBegin(GL_TRIANGLES);glColor3f(1.0f, 0.0f, 0.0f);glVertex2f(-0.5f, -0.5f);glColor3f(0.0f, 1.0f, 0.0f);glVertex2f(0.5f, -0.5f);glColor3f(0.0f, 0.0f, 1.0f);glVertex2f(0.0f, 0.5f);glEnd();}
};// 在主窗口中使用
setCentralWidget(new GLWidget);

10.2 QML混合开发

QWidget与QML集成

// 创建QQuickWidget
QQuickWidget *qmlWidget = new QQuickWidget;
qmlWidget->setSource(QUrl("qrc:/main.qml"));
qmlWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);// 添加到布局
ui->verticalLayout->addWidget(qmlWidget);// 从C++调用QML
QObject *root = qmlWidget->rootObject();
root->setProperty("text", "来自C++的消息");// 从QML调用C++
QObject::connect(root, SIGNAL(qmlSignal(QString)),this, SLOT(handleQmlSignal(QString)));

10.3 跨平台适配技巧

平台特定代码处理

// 检测平台
#if defined(Q_OS_WIN)// Windows特定代码QSettings settings("HKEY_CURRENT_USER\\Software", QSettings::NativeFormat);
#elif defined(Q_OS_MAC)// macOS特定代码QMenuBar::setNativeMenuBar(true);
#elif defined(Q_OS_LINUX)// Linux特定代码
#endif// 平台特定样式
#ifdef Q_OS_MACsetStyleSheet("QToolButton { padding: 5px; }");
#endif// 高DPI支持
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

结语

通过本文的系统学习,你已经掌握了Qt UI开发的核心技能体系。从基础布局到高级特效,从对话框设计到跨平台适配,这些知识将帮助你在实际项目中构建专业级用户界面。

进阶学习路线

  1. 性能优化:学习界面渲染优化技巧
  2. 测试驱动:掌握Qt Test框架进行UI自动化测试
  3. 设计模式:应用MVVM等架构设计复杂界面
  4. 开源项目:参与KDE等开源Qt项目积累经验
  5. 社区互动:加入Qt官方论坛和中文社区交流

实践是最好的老师!立即动手创建你的第一个Qt应用,在评论区分享你的作品。点赞收藏本文,成为你Qt开发路上的常备参考书!

感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

http://www.dtcms.com/a/326747.html

相关文章:

  • Android14 QS编辑页面面板的加载解析
  • 梯度裁剪总结
  • Python Day27 HTML 核心知识笔记及例题分析
  • 09-docker镜像手动制作
  • PG靶机 - Flu
  • 常见鱼饵制作方式
  • 在 X86_64(amd64) 平台上的docker支持打包构建多环境镜像并推送镜像到Harbor
  • AI Coding 概述及学习路线图
  • uploader组件,批量上传怎么设置实时滚动
  • Anti-Aliasing/Mip-NeRF/Zip-NeRF/multi-scale representation
  • 2.一维码+二维码+字符识别
  • OpenHarmony概述与使用
  • 基于大数据的个性化学习环境构建的研究与应用
  • Java前后端交互核心技术:Servlet与JSP深度解析
  • 【Altium designer】一键给多个器件添加参数
  • 2025年渗透测试面试题总结-13(题目+回答)
  • 如何选择一家靠谱的开发公司开发项目呢?
  • sql select语句
  • Python 高阶函数:filter、map、reduce 详解
  • WebMCP 技术文档——让 AI 助手与 Web 应用无缝交互的轻量级框架
  • 基于cursor工具与AI大模型,规范驱动的全自然语言软件开发工作流实现路径
  • 导入CSV文件到MySQL
  • webpark》》
  • STM32CubeMX + HAL 库:用硬件IIC接口实现AT24C02 EEPROM芯片的读写操作
  • Kubernetes部署apisix的理论与最佳实践(一)
  • 【OpenGL】LearnOpenGL学习笔记06 - 坐标系统、MVP变换、绘制立方体
  • 用 t-SNE 把 KSC 高光谱“变成可转动的 3D 影像”——从零到会,逐段读懂代码并导出旋转 GIF
  • 二叉树进阶 之 【模拟实现二叉搜索树】(递归、非递归实现查找、插入、删除功能)
  • 跨平台RTMP推流SDK vs OBS:技术差异与行业落地解析
  • 01数据结构-十字链表和多重邻接表