1.QMenu

在这里插入图片描述
一般窗口都有一个退出键,而在没有这个退出键的时候怎么退出?
QMenu可以解决这一点,示例如下:

信号 触发时机
QMenu::triggered 菜单(或其子菜单)中的任意 action 被触发,只要菜单里任何一项被点击都会进来
QAction::triggered 这个特定的 action 被触发,只有点击特定才会触发

在这里插入图片描述

在这里插入图片描述
右击出现菜单,点击任意,即可关闭窗口
在这里插入图片描述

2.Qmap

QMap 是 Qt 提供的一个关联容器类,用于存储键值对(key-value),它非常类似于 C++ 标准库的 std::map

核心特点

自动排序:按键升序存储(默认使用 < 运算符)

对数时间复杂度:查找、插入、删除都是 O(log n)

键唯一:每个键只能对应一个值

内存效率高:比 QHash 占用更少内存(无额外哈希表开销)

支持范围操作:可用 lowerBound/upperBound 获取范围内的键

排序规则是 字典序(lexicographical order),具体取决于 QString::operator< 的实现,它逐个字符比较 Unicode 码点(即区分大小写,且数字、字母、中文等都有确定的顺序)
示例

QMap<QString, int> map;
map["banana"] = 1;
map["apple"] = 2;
map("Apple") = 3;   // 注意大写 A
map["100"] = 4;
map["20"] = 5;

// 遍历输出
for (auto it = map.begin(); it != map.end(); ++it)
    qDebug() << it.key() << it.value();

输出顺序(实际按 Unicode 码点比较)

"100" : 4      ← 字符 '1' 码点 49,最小
"20"  : 5      ← 字符 '2' 码点 50,比 '1' 大但比字母小
"Apple" : 3'A' 码点 65
"apple" : 2'a' 码点 97
"banana" : 1'b' 码点 98

常规操作示例

#include <QMap>
#include <QDebug>

// 创建
QMap<QString, int> map;

// 插入
map["apple"] = 3;      // 方式1
map.insert("banana", 5); // 方式2
map.insert("orange", 2);

// 遍历(按键升序自动排列:apple, banana, orange)
for (auto it = map.begin(); it != map.end(); ++it) {
    qDebug() << it.key() << ":" << it.value();
}

// 查找
if (map.contains("apple")) {
    int count = map["apple"];  // 3
}

// 获取值(带默认值)
int grapes = map.value("grape", 0);  // 不存在,返回0

// 修改
map["apple"] = 10;

// 删除
map.remove("banana");

// 获取所有键(或值)
QList<QString> keys = map.keys();
QList<int> values = map.values();

// 清空
map.clear();

map.begin() 返回一个迭代器,指向 QMap 中第一个(key 最小) 元素。
map.end() 返回一个迭代器,指向最后一个元素之后的位置(不指向实际元素),作为循环结束标志

3.QHash

QHash 是 Qt 框架中的一个模板类,用于实现哈希表(散列表)数据结构。它提供键-值对的存储与快速查找,平均时间复杂度为 O(1)。

核心特点

  • 快速查找:基于哈希函数直接定位,比 QMap(基于红黑树,O(log n))更快
  • 无序存储:元素顺序不固定,依赖于哈希值
  • 键唯一:每个键只能对应一个值
  • 支持动态扩容:自动调整内部桶数组大小

基本用法示例

#include <QHash>
#include <QDebug>

int main() {
    // 创建哈希表
    QHash<QString, int> hash;
    
    // 插入元素
    hash["apple"] = 5;
    hash.insert("banana", 3);
    hash.insert("orange", 7);
    
    // 访问元素
    qDebug() << hash["apple"];   // 输出 5
    qDebug() << hash.value("grape", 0); // 不存在返回默认值 0
    
    // 检查键是否存在
    if (hash.contains("banana")) {
        qDebug() << "Found banana";
    }
    
    // 遍历
    QHashIterator<QString, int> it(hash);
    while (it.hasNext()) {
        it.next();
        qDebug() << it.key() << ":" << it.value();
    }
    
    // 使用范围循环(C++11)
    for (auto key : hash.keys()) {
        qDebug() << key << hash[key];
    }
    
    // 删除元素
    hash.remove("orange");
    hash.clear();  // 清空所有
    
    return 0;
}

常用函数

函数 说明
insert(key, value) 插入键值对
value(key, defaultValue) 获取值,不存在返回默认值
contains(key) 判断键是否存在
remove(key) 删除指定键
keys() 返回所有键的列表
values() 返回所有值的列表
size() / isEmpty() 大小和判空
find(key) 查找并返回迭代器

自定义类型作为键,需要自定义哈希函数需实现 qHash

struct Person {
    QString name;
    int age;
    
    bool operator==(const Person &other) const {
        return name == other.name && age == other.age;
    }
};

// 为自定义类型实现哈希函数
uint qHash(const Person &p, uint seed = 0) {
    return qHash(p.name, seed) ^ qHash(p.age, seed + 1);
}

// 使用
QHash<Person, QString> hash;
hash[{ "Alice", 25 }] = "Engineer";

QMap 与 QHash 选择指南

使用 QMap :
  • 需要按键排序遍历:QMap 内部基于红黑树实现,元素按键排序存储
  • 需要进行范围查询:如查找所有键在 [a, b] 之间的元素
  • 内存有限,QHash 开销太大:QMap 通常比 QHash 更节省内存
  • 键类型不容易实现好的哈希函数:某些复杂类型难以实现高效的哈希函数
使用 QHash :
  • 追求最快的查找速度:哈希表平均时间复杂度 O(1),比 QMap 的 O(log n) 更快
  • 不关心元素顺序:哈希表不保证元素的存储顺序
  • 键类型有现成的 qHash 函数:如 QString、QByteArray 等 Qt 内置类型
  • 键数量非常大:哈希表在大数据量下平均性能更好

4.QPainter

QPainter 是 Qt 框架中用于 2D 绘图 的核心类。它可以在各种“画布”(称为 绘图设备 QPaintDevice)上绘制图形,包括窗口部件,如:QWidget

1.QPainter 的核心能力

  • 绘制基本图元:点、直线、矩形、椭圆、圆弧、多边形等
    绘制文本:支持字体、对齐、换行

  • 填充和渐变:纯色、线性渐变、径向渐变、纹理

  • 变换:平移、旋转、缩放、剪切(支持仿射变换)

  • 抗锯齿:使图形边缘平滑

  • 合成模式:控制新绘制内容与背景的混合方式

  • 裁剪区域:限制绘图范围

2.QPainter 使用的基本步骤

  • 创建 QPainter 对象,并传入一个 QPaintDevice(如 QWidget)。

  • 设置画笔(QPen) 和 画刷(QBrush) 以定义线条和填充样式。

  • 调用绘图函数(如 drawRect、drawLine、drawText)。

  • QPainter 对象通常会在 绘图事件(paintEvent) 中创建,并且绘制结束后会自动销毁

注意
QPainter 必须处于 激活(active) 状态才能进行绘图操作(如 drawRect、drawText)。激活意味着它已经与一个具体的绘图设备(QPaintDevice,比如 QWidget)关联起来

QPainter 只能在使用 QPainter 构造函数时激活,或者通过 begin() / end() 显式管理

示例

构造函数直接关联设备

QPainter painter(widget);   // 传入 QPaintDevice*,自动调用 begin()
painter.drawRect(10, 10, 100, 100);   // 可以直接绘图
// 析构时自动调用 end()

先构造无参对象,再手动调用 begin() / end()

QPainter painter;
painter.begin(widget);      // 手动激活,关联绘图设备
painter.drawRect(10, 10, 100, 100);
painter.end();              // 手动结束绘图

关键规则

  • 在 begin() 之后、end() 之前,QPainter 处于激活状态,可以绘图。
  • 不能嵌套激活:begin() 后再次调用 begin() 会出错。
  • 必须在 end() 之后才能重新 begin() 到另一个设备。
  • 忘记调用 end()(或者在析构前未调用)通常不会有严重后果,因为析构函数会自动调用 end(),但显式 end() 有利于资源及时释放。

简单示例
在 QWidget 上画一个红色矩形

// 自定义窗口类
class MyWidget : public QWidget {
protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);          // 1. 创建对象,画布是当前窗口
        painter.setPen(Qt::red);         // 2. 设置画笔(线条颜色)
        painter.setBrush(Qt::blue);      // 3. 设置画刷(填充颜色)
        painter.drawRect(50, 50, 100, 80); // 4. 绘制矩形(x, y, width, height)
    }
};

4.QPainter 核心组件

1.画笔(QPen)

控制线条的 颜色、宽度、样式(实线、虚线、点线等)、端点样式、连接样式。

QPen pen(Qt::green, 2, Qt::DashLine);   // 2px 绿色虚线
painter.setPen(pen);
Qt::PenStyle 枚举总结
枚举值 说明
Qt::NoPen 无线条(不绘制轮廓)
Qt::SolidLine 实线
Qt::DashLine 虚线(由若干短线组成)
Qt::DotLine 点线(由点组成)
Qt::DashDotLine 虚点线(短线和点交替)
Qt::DashDotDotLine 虚点点线(短线、点、点交替)
Qt::CustomDashLine 自定义虚线样式(需通过 QPen::setDashPattern() 设置)
2. 画刷(QBrush)

控制填充区域的 颜色、图案(斜线、网格等)、渐变、纹理。

QBrush(Qt::red)

函数原型

QBrush::QBrush(Qt::GlobalColor color)

参数说明

color:Qt::GlobalColor 枚举值,如 Qt::red、Qt::blue、Qt::green 等。表示标准预定义颜色。

示例

QPainter painter(this);
painter.setBrush(QBrush(Qt::red));
painter.drawRect(10, 10, 100, 100);   // 绘制一个红色实心矩形
QBrush(Qt::blue, Qt::DiagCrossPattern)

函数原型

QBrush::QBrush(Qt::GlobalColor color, Qt::BrushStyle style)
// 或 QBrush::QBrush(const QColor &color, Qt::BrushStyle style)

参数说明

color:画刷的基本颜色(线条或底色)。

style:Qt::BrushStyle 枚举值,指定填充图案。Qt::DiagCrossPattern 表示双斜线交叉图案(米字形网格)。

示例

QPainter painter(this);
painter.setBrush(QBrush(Qt::blue, Qt::DiagCrossPattern));
painter.drawRect(10, 10, 100, 100);   // 蓝色交叉斜线填充的矩形
QBrush(pixmap)

函数原型

QBrush::QBrush(const QPixmap &pixmap)

参数说明

pixmap:QPixmap 对象,用作纹理图样。该图片会平铺(tile)填充整个区域。

创建一个纹理画刷。当用它填充形状时,pixmap 会像瓷砖一样重复平铺。纹理的起始点默认为设备坐标原点 (0,0)。

示例

QPixmap tile(":/images/pattern.png");   // 小纹理图
QPainter painter(this);
painter.setBrush(QBrush(tile));
painter.drawRect(10, 10, 200, 150);     // 矩形内平铺 pattern.png
QBrush(gradient)

函数原型

QBrush::QBrush(const QGradient &gradient)

参数说明

gradient:QGradient 对象(或其子类 QLinearGradient、QConicalGradient、QRadialGradient),定义颜色在区域内的渐变规则。

创建一个渐变画刷。填充时颜色会按照渐变规则平滑过渡。可用于绘制立体感或光影效果。具体可见高级特性中的渐变填充

Qt::BrushStyle 枚举总结
枚举值 说明
Qt::NoBrush 无填充(透明)
Qt::SolidPattern 纯色实心填充
Qt::Dense1Pattern 极稀疏的点阵
Qt::Dense2Pattern 稀疏的点阵
Qt::Dense3Pattern 较稀疏的点阵
Qt::Dense4Pattern 中等密度的点阵
Qt::Dense5Pattern 较密集的点阵
Qt::Dense6Pattern 密集的点阵
Qt::Dense7Pattern 极密集的点阵(接近实心)
Qt::HorPattern 水平线
Qt::VerPattern 垂直线
Qt::CrossPattern 水平+垂直线网格
Qt::BDiagPattern 反斜线(\)
Qt::FDiagPattern 斜线(/)
Qt::DiagCrossPattern 双斜线交叉(米字)
Qt::LinearGradientPattern 线性渐变
Qt::RadialGradientPattern 径向渐变
Qt::ConicalGradientPattern 锥形渐变
Qt::TexturePattern 纹理填充
函数总结
画刷构造方式 填充效果
QBrush(Qt::red) 纯红色
QBrush(Qt::blue, Qt::DiagCrossPattern) 蓝色底+斜线图案
QBrush(pixmap) 图片平铺(纹理)
QBrush(gradient) 渐变填充
理解区分,drawPixmap和setBrush(QBrush(QPixmap))
drawPixmap:单次绘制原始图片,位置、大小由参数决定,不会平铺。

setBrush(QBrush(pixmap)):设置纹理画刷,后续绘制任何形状(矩形、圆形等)时,会用该图片平铺填充整个形状区域,图片重复出现。图片,图片会重复出现(类似瓷砖)。

所以如果你想让图片单次绘制,直接用 drawPixmap 或 drawImage。如果你想让图片重复铺满一个区域,就用 setBrush(QBrush(pixmap))

3. 字体(QFont)

控制文本的 字体族、大小、粗体、斜体 等。

QFont font("Arial", 16, QFont::Bold);
painter.setFont(font);
painter.drawText(100, 100, "Hello Qt");
4. 坐标系统
  • 默认原点 (0,0) 在绘图设备的左上角,默认坐标系:x 轴向右,y 轴向下
  • 可通过 平移(translate)、旋转(rotate)、缩放(scale) 变换坐标系。
  • 可以使用 save() / restore() 保存/恢复坐标系状态
translate --平移坐标系

函数原型

void translate(const QPointF &offset);
void translate(qreal dx, qreal dy);
void translate(const QPoint &offset);
void translate(int dx, int dy);

参数说明
dx / dy 或 offset:将坐标原点沿 X 轴、Y 轴移动的距离(支持整数或浮点数)。

作用
将当前坐标系的原点移动到指定位置。后续所有绘图坐标都基于新的原点。

示例

QPainter painter(this);
painter.drawPoint(0, 0);          // 在原位置 (0,0) 画点
painter.translate(100, 50);
painter.drawPoint(0, 0);          // 实际画在 (100,50) 处
rotate – 旋转坐标系

函数原型

void rotate(qreal angle);

参数说明
angle:旋转角度,单位为度,正值表示顺时针旋转(默认坐标系 X 轴向右、Y 轴向下)。

作用
将坐标系绕当前原点顺时针旋转 angle 度。

示例

painter.translate(100, 100);
painter.rotate(45);               // 顺时针45度
painter.drawRect(-50, -50, 100, 100); // 画一个正方形,实际显示为旋转45度的菱形
scale – 缩放坐标系

函数原型

void scale(qreal sx, qreal sy);

参数说明
sx:X 轴缩放因子(>1 放大,0~1 缩小,负数产生镜像翻转)。
sy:Y 轴缩放因子。

作用
将坐标系在水平和垂直方向进行缩放。所有后续绘制的图形尺寸都会随之缩放。

示例

painter.scale(2.0, 0.5);          // X 方向放大2倍,Y 方向缩小为0.5倍
painter.drawRect(10, 10, 100, 100); //相当于从(20,5)开始绘图,矩形的大小是宽200,高50 

注解
painter.scale(2.0, 0.5) 将坐标系在 X 方向放大 2 倍,Y 方向缩小为 0.5 倍。随后 drawRect(10, 10, 100, 100) 中的坐标和尺寸都会受当前变换影响:

矩形左上角原 (10, 10) 变为 (10×2, 10×0.5) = (20, 5)

宽度 100 变为 100×2 = 200

高度 100 变为 100×0.5 = 50

所以相当于从(20,5)开始绘图,矩形的大小是宽200,高50

shear —— 切变(错切)坐标系

函数原型

void shear(qreal sh, qreal sv);

参数说明
sh:水平切变系数(表示 Y 坐标对 X 坐标的偏移影响,即新 X = 原 X + sh * 原 Y)。

sv:垂直切变系数(表示 X 坐标对 Y 坐标的偏移影响,即新 Y = 原 Y + sv * 原 X)。

作用
对坐标系应用切变变换,可将矩形变为平行四边形。

示例

painter.shear(0.5, 0);            // 水平切变,矩形变成平行四边形
painter.drawRect(50, 50, 100, 100);

注解
矩形 (50, 50, 100, 100) 的四个角

原始坐标 (x,y) 应用 shear(0.5, 0) 后
(50,50) 左上 (75, 50)
(150,50) 右上 (175, 50)
(50,150) 左下 (125, 150)
(150,150) 右下 (225, 150)

原始正方形:            错切后:
(50,50)---(150,50)      (75,50)-------(175,50)
 |           |            \               \
 |           |              \               \
(50,150)--(150,150)        (125,150)-----(225,150)
setTransform – 直接设置变换矩阵

函数原型

void setTransform(const QTransform &transform, bool combine = false);

参数说明
transform:一个 QTransform 矩阵对象(包含平移、旋转、缩放、切变所有信息)。

combine:若为 false(默认),替换当前变换;若为 true,则将新矩阵与当前变换相乘组合。

作用
完全控制坐标变换矩阵,适合复杂或非标准的变换。

示例

QTransform transform;
transform.translate(100, 100);
transform.rotate(45);
transform.scale(1.5, 1.5);
painter.setTransform(transform);   // 一次性应用平移+旋转+缩放
painter.drawRect(-50, -50, 100, 100);
resetTransform – 重置变换

函数原型

void resetTransform();

无参数

作用
清除所有坐标变换,将坐标系恢复为默认状态(单位矩阵)。

示例

painter.translate(200, 200);
painter.rotate(90);
// ... 画一些东西
painter.resetTransform();          // 回到原始坐标系
painter.drawPoint(0, 0);           // 在窗口左上角(0,0)画点
函数总结表

在这里插入图片描述

函数 参数 作用
translate(dx, dy) 偏移量 移动原点
rotate(angle) 角度(度,正顺时针) 绕原点旋转
scale(sx, sy) 缩放因子 缩放坐标系
shear(sh, sv) 切变系数 错切变换
setTransform(t, combine) 矩阵,是否组合 设置整个变换矩阵
resetTransform() 恢复为单位矩阵
save() / restore() 保存/恢复所有状态(不仅是变换)

5、常用绘图函数

函数 描述
drawPoint(x, y) 绘制点
drawLine(x1, y1, x2, y2) 绘制直线
drawRect(x, y, w, h) 绘制矩形
drawRoundedRect(…) 圆角矩形
drawEllipse(x, y, w, h) 椭圆
drawArc(…) 圆弧
drawPolygon(points) 多边形(自动闭合)
drawText(x, y, text) 绘制文本 (x,y 为基线坐标)
drawPixmap(x, y, pixmap) 绘制图片
drawPath(path) 绘制由 QPainterPath 定义的复杂路径

drawPolygon(points)

drawPolygon(points) 是 QPainter 中用于绘制多边形的函数。它会根据传入的点集,依次连接各点(首尾自动闭合),并用当前画笔(QPen)描边,用当前画刷(QBrush)填充内部。
void MyWidget::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setPen(Qt::blue);
    painter.setBrush(Qt::yellow);

    // 定义三个顶点 (三角形)
    QPolygon triangle;
    triangle << QPoint(50, 50) << QPoint(150, 50) << QPoint(100, 120);
    painter.drawPolygon(triangle);
}

QPainterPath

QPainterPath 是一个图形组合容器,可以将多个图形元素(线段、矩形、椭圆、曲线等)组合成一个复合路径,并且可以执行布尔运算(并集、差集等)。

作用:预先定义好一系列绘图指令,然后通过 drawPath 一次性绘制
优势:可以复用、缓存路径;支持复杂的填充规则;容易进行坐标变换。

void MyWidget::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setPen(Qt::red);
    painter.setBrush(Qt::blue);

    // 创建一个 QPainterPath
    QPainterPath path;

    // 添加一个矩形
    path.addRect(50, 50, 100, 80);
    // 添加一个椭圆(与矩形相交)
    path.addEllipse(80, 70, 80, 60);

    // 一次性绘制整个路径,填充会按填充规则填充
    painter.drawPath(path);
}

在这里插入图片描述
默认填充规则是,重叠区域不填充

运行效果
在这里插入图片描述

6.高级特性

抗锯齿

是 Qt 中 QPainter 的一个设置函数,用于开启抗锯齿(Antialiasing)渲染,使绘制的图形边缘更平滑,消除锯齿状的阶梯效应。

 painter.setRenderHint(QPainter::Antialiasing,true);
渐变填充
    QPainter painter(this);
    QLinearGradient gradient(0, 0, 100, 100);//创建渐变对象,起点 (0, 0),终点 (100, 100)
    gradient.setColorAt(0, Qt::white);
    gradient.setColorAt(1, Qt::black);
    painter.setBrush(gradient);
    painter.drawRect(0, 0, 100, 100);

注解

1.创建渐变对象

  • QLinearGradient gradient(0, 0, 100, 100);

  • QLinearGradient 表示线性渐变,即颜色沿着一条直线方向平滑变化。

      参数 (0, 0, 100, 100) 定义了渐变的起点和终点:
      起点 (0, 0):逻辑坐标,通常对应矩形的左上角。
      终点 (100, 100):逻辑坐标,对应矩形的右下角(矩形宽高各为100)。
    

渐变方向是从起点到终点的直线,本例中为从左上到右下的对角线。

2. 设置颜色停止点

  • gradient.setColorAt(0, Qt::white);

  • gradient.setColorAt(1, Qt::black);

      setColorAt(position, color):在渐变路径的某个位置(0到1之间)定义颜色。
      position = 0 表示起点((0,0))处的颜色为白色。
      position = 1 表示终点((100,100))处的颜色为黑色。
      中间位置的颜色会自动插值(例如位置0.5处为灰色)。
    

可以设置多个颜色停止点,例如 setColorAt(0.5, Qt::red) 实现更复杂的渐变。

3. 将渐变设置为画刷

  • painter.setBrush(gradient);
  • QPainter::setBrush() 设置填充画刷。画刷可以是纯色、纹理、渐变等。

这里将上面定义的线性渐变作为画刷,后续的图形(如矩形)将使用此渐变填充。

4. 绘制矩形

  • painter.drawRect(0, 0, 100, 100);
    绘制一个矩形,左上角在 (0,0),宽度 100,高度 100。

矩形内部用当前画刷(即对角线渐变)填充

运行结果
在这里插入图片描述

7.paintEvent 的什么时候触发?

  • 窗口首次显示:调用 show() 或窗口从隐藏变为可见。

  • 窗口大小改变:用户拖动边缘或代码调用 resize()。

  • 窗口从被遮挡到重新暴露:其他窗口移开、最小化恢复、切换标签页等。
    如:窗口收起来,然后再打开,会触发paintEvent
    在这里插入图片描述

  • 代码主动请求重绘:

      update():异步,最常用的人为触发方式(不会立即重绘,而是投递一个绘制事件到事件队列)
      
      repaint():同步,立即重绘(会直接触发 paintEvent,不经过事件循环)
    
  • 系统属性变化:修改样式表(QSS)、字体、调色板时,系统自动触发。

  • 父部件重绘传播:父部件重绘时可能连带子部件(取决于属性设置)。

核心:只要 Qt 认为窗口内容“需要刷新”,就会调用 paintEvent。绝大部分情况下,你只需要调用 update() 来触发它

8.示例

这里是给指定的控件绘图,所以没用 paintEvent()(给整个窗口添加),会导致覆盖,所以用了bool eventFilter(QObject *watched,QEvent *event);(事件过滤),事件相关知识可见事件

bool eventFilter(QObject *watched,QEvent *event);

bool eventFilter(QObject * watched,QEvent *event)

  • watched:被监视的对象,即事件原本要发送给的对象。
  • event:将要被处理的事件(如鼠标点击、键盘按下、定时器事件等)。

返回值:

  • true:表示事件已被你处理,不再继续传递给目标对象。
  • false:表示事件未被处理(或你希望继续正常处理),Qt 会把它继续发送给目标对象。

在这里插入图片描述
给窗口安装过滤器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里窗口运行,会绘制一次,但是不是用户想要的曲线,所以调用update()再一次绘制,得到正确的曲线
在这里插入图片描述
在这里插入图片描述
运行效果
在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐