项目:停车场车辆管理系统

这个代码实现了一个停车场管理系统,主要功能包括车辆信息的添加、删除、修改、查找、显示所有车辆信息、排序以及计算停车费用。系统使用双向链表来存储车辆数据,并提供了菜单驱动的界面供用户选择不同的操作。

主要功能描述:

  1. 添加车辆信息

    用户可以选择添加新的车辆信息,包括车牌号、车辆类型、停放时间等。
  2. 删除车辆信息

    通过输入车牌号,可以从系统中删除相应的车辆记录。
  3. 修改车辆信息

    用户可以修改指定车牌号的车辆信息,如更新停放时间或车辆类型。
  4. 查找车辆信息

    根据输入的车牌号,查找并显示该车辆的详细信息。
  5. 显示所有车辆

    列出当前系统中所有已登记的车辆信息。
  6. 排序功能

    用户可以选择按停放时间或车辆类型对车辆进行排序,并显示排序后的结果。
  7. 计算停车费用

    根据车辆的停放时间计算停车费用。
  8. 数据持久化

    系统能够从文件中加载车辆数据,并在退出时将数据保存到文件中,以实现数据的持久化。

实现方式:

  • 数据结构

    使用双向链表(SLIST)来存储车辆数据,便于插入、删除和遍历操作。
  • 文件操作

    通过load_cars_from_filesave_cars_to_file函数实现数据的读取和保存,使用二进制文件格式。
  • 用户交互

    提供菜单选项,用户可以通过输入数字选择相应的操作,界面清晰,操作简便。
  • 临时排序

    在排序功能中,创建临时链表复制原始数据进行排序,确保原始数据不被修改,排序后显示结果并释放临时链表。

代码结构:

  • 主函数

    初始化链表,加载数据,显示菜单,处理用户选择,执行相应操作,保存数据并释放资源。
  • 辅助函数

    包括链表操作(初始化、添加、删除、查找等)、数据输入输出、排序、费用计算等功能,模块化设计,便于维护和扩展。

代码演示:

car.h

功能:用于声明车辆数据结构和计费功能函数。头文件的主要作用是提供数据结构和函数声明,以便在其他源文件中使用这些定义和声明。

// 定义车辆数据结构和计费功能函数声明
#ifndef CAR_H
#define CAR_H#define MAX_STRING_LENGTH 100typedef struct
{char carnumber[MAX_STRING_LENGTH]; // 车牌号char brand[MAX_STRING_LENGTH];     // 品牌char model[MAX_STRING_LENGTH];     // 型号char cartype[MAX_STRING_LENGTH];   // 车辆类型int parktime;                      // 停车时间
} DATA;float calculate_fee(int parktime);#endif

car.c:计费功能实现

           规则:不满1小时不收费,超过1小时按10元每小时收费

// 计费功能实现
#include "car.h"float calculate_fee(int parktime)
{if (parktime <= 1){return 0.0; // 前一个小时免费}else{return (parktime - 1) * 10.0; // 1小时后每小时计费10元}
}

function.h : 进行菜单函数定义和功能函数声明

// 头文件,声明函数原型
#ifndef FUNCTION_H
#define FUNCTION_H#include "slist.h"// 菜单函数
void display_menu();
// 录入车辆数据函数
void input_car_data(DATA *data);
// 显示车辆数据函数
void display_car_data(DATA *data);
// 保存文件数据函数
void save_cars_to_file(const char *filename, SLIST *list);
// 读取文件数据函数
void load_cars_from_file(const char *filename, SLIST *list);
// 停放时间排序函数
void sort_cars_by_parking_time(SLIST *list);
// 车辆类型排序函数
void sort_cars_by_type(SLIST *list);
// 缓存链表创建函数
void copy_list(SLIST *dest, SLIST *src);#endif

function.c:实现主界面显示和文件IO(写入、加载),以及排序算法功能

// 实现主界面显示和文件IO、排序算法
#include "function.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>void display_menu()
{// 设置终端颜色为绿色printf("\033[1;36m");printf("    ______                ______\n");printf("   /|_||_\\`.__          /|_||_\\`.__\n");printf("  (   _    _ _\\        (   _    _ _\\\n");printf("  =`-(_)--(_)-' ______ =`-(_)--(_)-'\n");// 重置终端颜色printf("\033[0m\n");// 设置终端颜色为蓝色printf("\033[1;34m");printf("========================================\n");printf("=                                      =\n");printf("=         停车场车辆管理系统           =\n");printf("=     🚕         🚕          🚕        =\n");printf("========================================\n");// 重置终端颜色printf("\033[0m\n");printf("\n");printf("\033[1;96m    ------------------------------\033[0m \n");printf("\033[1;96;5m"); // 5是闪烁效果printf("              /\\_____/\\       -----------------------------\n");printf("             /  o   o  \\ ---  | Welcome to Parking System |\n");printf("            ( ==  ^  == )     -----------------------------\n");printf("             )----O----(\n");printf("            (           )\n");printf("           ( (  )   (  ) )\n");printf("          (__(__)___(__)__) \n");printf("\033[0m"); // 重置样式printf("\033[1;96m    ------------------------------\033[0m \n");printf("\n");// 设置终端颜色为黄色printf("\033[1;35m");printf("+--------------------------------------+\n");printf("| 1. 添加车辆信息..................... |\n");printf("| 2. 删除车辆信息..................... |\n");printf("| 3. 修改车辆信息..................... |\n");printf("| 4. 查找车辆信息..................... |\n");printf("| 5. 显示所有车辆..................... |\n");printf("| 6. 排序功能实现..................... |\n");printf("| 7. 计算停车费用..................... |\n");printf("| 0. 退出............................. |\n");printf("+--------------------------------------+\n");// 重置终端颜色printf("\033[0m");
}// 定义输入车辆数据函数
void input_car_data(DATA *data)
{printf("输入车牌号: ");scanf("%s", data->carnumber);printf("输入品牌: ");scanf("%s", data->brand);printf("输入型号: ");scanf("%s", data->model);printf("输入车辆类型: ");scanf("%s", data->cartype);printf("输入停放时间(小时): ");scanf("%d", &data->parktime);
}// 定义显示车辆数据函数
void display_car_data(DATA *data)
{printf("车牌号: %s\n", data->carnumber);printf("品牌: %s\n", data->brand);printf("型号: %s\n", data->model);printf("车辆类型: %s\n", data->cartype);printf("停车时长: %d 小时\n", data->parktime);float fee = calculate_fee(data->parktime);printf("停车费用: %.2f 元\n", fee);printf("------------------------\n");
}// 定义将链表中的数据保存到文件中的函数
void save_cars_to_file(const char *filename, SLIST *list)
{// const char *filename:要保存的文件名。// SLIST *list:要保存数据的链表。FILE *file = fopen(filename, "wb");if (file == NULL){printf("无法打开文件 %s\n", filename);return;}NODE *current = list->head;// 遍历链表,写入数据到链表中while (current != NULL){fwrite(&current->data, sizeof(DATA), 1, file);current = current->next;}fclose(file);
}// 定义了从指定文件中读取数据并加载到链表中的函数
void load_cars_from_file(const char *filename, SLIST *list)
{// const char *filename:要加载的文件名// SLIST *list :要加载数据到的链表FILE *file = fopen(filename, "rb");if (file == NULL){printf("无法打开文件 %s\n", filename);return;}init_list(list);while (1){DATA data;if (fread(&data, sizeof(DATA), 1, file) != 1){break;}add_car(list, data);}fclose(file);
}// 使用插入排序,对停车时长进行排序
void sort_cars_by_parking_time(SLIST *list)
{if (list->head == NULL || list->head->next == NULL){return; // 空链表或只有一个节点,无需排序}NODE *sorted = NULL;// 创建新的链表sorted,用于存储已排序的节点,初始化为NULLNODE *current = list->head;// 将current指针初始化为链表的头结点while (current != NULL){/*** 在处理当前节点之前,先将next指针指向当前节点的下一个节点,* 避免处理节点数据时丢失对下一个节点的引用*/NODE *next = current->next;/*** 情况1:当前节点为第一个节点或停车时间最小*/if (sorted == NULL || current->data.parktime < sorted->data.parktime)/*** 如果 sorted 为空(即当前节点是第一个节点),或者当前节点的* 停车时间小于排序后链表头结点的停车时间,则将当前节点插入到sorted链表的头部*/{current->next = sorted; // 将当前节点的next指针指向sortedsorted = current;       // 更新sorted为当前节点}/*** 情况2:当前节点需要插入到已排序链表的中间或者尾部*/else{NODE *temp = sorted; // 将temp指针指向新创建的sorted链表while (temp->next != NULL && current->data.parktime >= temp->next->data.parktime)/*** 使用 temp 指针从 sorted 链表的头节点开始,依次检查每个节点* 的停车时间,直到找到一个节点的停车时间大于或等于当前节点的停车时间*/{temp = temp->next;}current->next = temp->next;// 将当前节点的next指针指向temp->next,然后将temp->next指向// 当前节点,完成插入排序操作temp->next = current;}current = next;// 将current指针移动到下一个节点(next),继续遍历列表}list->head = sorted; // 将链表的头指针变为sorted,表示排序操作完成
}// 使用插入排序,对同车型的车辆进行排序处理
void sort_cars_by_type(SLIST *list)
{if (list->head == NULL || list->head->next == NULL) // 头结点为空,或者只有一个节点{return; // 空链表或只有一个节点,无需排序,直接返回}NODE *sorted = NULL; // 创建新链表sorted,用于存储排序后的节点NODE *current = list->head;while (current != NULL){NODE *next = current->next;// 将next指针指向当前节点的下一个节点,防止丢失节点引用if (sorted == NULL || strcmp(current->data.cartype, sorted->data.cartype) < 0){// 将当前节点的 next 指针指向 sorted,然后将 sorted 更新为当前节点。current->next = sorted;sorted = current;}else{NODE *temp = sorted;while (temp->next != NULL && strcmp(current->data.cartype, temp->next->data.cartype) >= 0){temp = temp->next;}current->next = temp->next;temp->next = current;}current = next;}list->head = sorted;
}// 创建copy链表函数(深拷贝)
void copy_list(SLIST *dest, SLIST *src)
// SLIST *dest:目标链表,用于存储复制后的节点
// SLIST *src:源链表,需要被复制的链表
{NODE *current = src->head; // 将current指针初始化为源链表(src)的头结点,用于遍历链表while (current != NULL){add_car(dest, current->data);// 调用函数,将当前节点的数据添加到目标链表的尾部。current = current->next;}
}

slist.h:定义了一个单链表数据结构及其相关函数声明,并将其封装在一个头文件 slist.h 中。头文件的主要作用是提供链表的数据结构定义和函数声明,以便在其他源文件中使用这些定义和声明。

// 单链表数据结构和函数声明
#ifndef SLIST_H
#define SLIST_H#include "car.h"typedef struct Node
{DATA data;struct Node *next;
} NODE;typedef struct
{NODE *head;
} SLIST;void init_list(SLIST *list);
void add_car(SLIST *list, DATA data);
void remove_car(SLIST *list, char *carnumber);
void update_car(SLIST *list, char *carnumber, DATA new_data);
void find_car(SLIST *list, char *carnumber);
void display_all_cars(SLIST *list);
void free_list(SLIST *list);
void sort_cars_by_parking_time(SLIST *list);
void sort_cars_by_type(SLIST *list);#endif

slist.c:实现了单链表的基本操作,包括初始化、添加、删除、修改、查询、显示所有节点以及释放链表内存等功能

// 实现单链表的相关操作
#include "slist.h"
#include "function.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>void init_list(SLIST *list)
{list->head = NULL;
}
// 添加车辆信息函数(头插)
void add_car(SLIST *list, DATA data)
// SLIST *list:指向单链表的指针
{NODE *new_node = (NODE *)malloc(sizeof(NODE));// 使用malloc存储NODE结构体if (new_node == NULL) // malloc返回NULL{printf("内存分配失败\n");return;}new_node->data = data;// 将传入的data赋值给新节点的datanew_node->next = list->head;// 将当前链表的头节点赋值给新节点的 next 指针,表示新节点接下来连接原来的头节点。list->head = new_node;// 链表头指针指向新节点
}// 删除车辆信息函数
void remove_car(SLIST *list, char *carnumber)
{NODE *current = list->head;// current:指向当前遍历的节点,初始时指向链表的头节点。NODE *previous = NULL;// previous:指向 current 节点的前驱节点,初始时为 NULL,因为链表的头节点没有前一个节点。while (current != NULL){if (strcmp(current->data.carnumber, carnumber) == 0){if (previous == NULL) // 匹配的节点是链表的头节点{list->head = current->next; // 将链表的头指针指向当前节点的下一个节点}else{previous->next = current->next; // 将前一个节点的next指针指向当前节点的下一个节点}free(current);return;}previous = current;      // 将prev指针更新为当前节点current = current->next; // 将当前节点更新为当前节点的下一个节点}printf("未找到车牌号为 %s 的车辆\n", carnumber);
}// 修改车辆信息函数
void update_car(SLIST *list, char *carnumber, DATA new_data)
{NODE *current = list->head;// 将current指针初始化为链表的头结点,开始遍历链表while (current != NULL){if (strcmp(current->data.carnumber, carnumber) == 0){current->data = new_data;return;}current = current->next;}printf("未找到车牌号为 %s 的车辆\n", carnumber);
}// 查询车辆信息函数
void find_car(SLIST *list, char *carnumber)
{NODE *current = list->head;while (current != NULL){if (strcmp(current->data.carnumber, carnumber) == 0){display_car_data(&current->data);return;}current = current->next;}printf("未找到车牌号为 %s 的车辆\n", carnumber);
}// 显示所有车辆函数
void display_all_cars(SLIST *list)
{NODE *current = list->head;while (current != NULL){display_car_data(&current->data);// 传入当前节点的数据的地址printf("\n");current = current->next;}
}void free_list(SLIST *list)
{NODE *current = list->head;NODE *next = NULL; // 将next指针初始化为NULL,用于临时保存下一节点的地址。while (current != NULL){// 将next指针指向当前节点的下一个节点next = current->next;free(current);current = next;// 将current指针移动到下一个节点,继续遍历}list->head = NULL; // 表示链表已清空,因为头指针为NULL
}

main.c:main.c是一个基于单链表的车辆管理系统的主程序,提供了用户交互界面,并实现了车辆信息的增删改查、排序、停车费用计算等功能。

#include "function.h"
#include <stdio.h>
#include <stdlib.h>int main()
{SLIST list;init_list(&list);int choice;DATA data;load_cars_from_file("cars.dat", &list);do{display_menu();printf("\033[1;31m");printf("请输入选项: ");scanf("%d", &choice);printf("------------------------\n");switch (choice){case 1: // 添加车辆信息input_car_data(&data);add_car(&list, data);break;case 2: // 删除车辆信息printf("输入要删除的车牌号: ");char carnumber[100];scanf("%s", carnumber);remove_car(&list, carnumber);break;case 3: // 修改车辆信息printf("输入要修改的车牌号: ");scanf("%s", carnumber);printf("输入新的车辆信息:\n");input_car_data(&data);update_car(&list, carnumber, data);break;case 4: // 查找车辆信息printf("输入要查找的车牌号: ");scanf("%s", carnumber);find_car(&list, carnumber);break;case 5: // 显示所有车辆display_all_cars(&list);break;case 6: // 排序功能实现printf("选择排序方式:\n");printf("1. 按停放时间排序\n");printf("2. 按车辆类型排序\n");printf("输入选项: ");int sort_choice;scanf("%d", &sort_choice);SLIST temp_list; // 创建临时链表init_list(&temp_list);copy_list(&temp_list, &list); // 复制原始链表到临时链表if (sort_choice == 1){sort_cars_by_parking_time(&temp_list); // 对临时链表排序}else if (sort_choice == 2){sort_cars_by_type(&temp_list); // 对临时链表排序}else{printf("无效的选项\n");free_list(&temp_list); // 释放临时链表break;}display_all_cars(&temp_list); // 显示排序后的临时链表free_list(&temp_list);        // 释放临时链表printf("排序完成!\n");break;case 7: // 计算停车费用printf("输入车牌号: ");scanf("%s", carnumber);find_car(&list, carnumber);break;case 0: // 退出save_cars_to_file("cars.dat", &list);free_list(&list);printf("退出系统\n");break;default:printf("无效的选项\n");}} while (choice != 0);return 0;
}

多文件编译:

gcc -o parking main.c function.c car.c slist.c

执行:

./parking

界面演示:

说明:

可通过输入序号对具体车辆信息进行增添删改操作,也可通过排序功能选择不同的排序算法对车辆进行系统性排序。

总结

该程序通过菜单驱动的方式,提供了对车辆信息的多种管理功能。使用单链表存储数据,确保了数据的动态性。通过文件操作,实现了数据的持久化存储。排序功能通过创建临时链表进行,避免直接修改原始数据。整体结构清晰,功能完善,适合小型车辆管理系统使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/501297.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RS485方向自动控制电路分享

我们都知道RS485是半双工通信&#xff0c;所以在传输的时候需要有使能信号&#xff0c;标明是发送还是接收信号&#xff0c;很多时候就简单的用一个IO口控制就好了&#xff0c;但是有一些低成本紧凑型的MCU上&#xff0c;一个IO口也是很珍贵的&#xff0c;因此&#xff0c;如果…

《代码随想录》Day24打卡!

《代码随想录》回溯算法&#xff1a;复原IP地址 本题的完整题目如下&#xff1a; 本题的完整思路如下&#xff1a; 1.本题使用递归以及回溯来做&#xff0c;所以依然分为三部曲&#xff1a; 2.第一步&#xff1a;确定递归的参数和返回值&#xff1a;无返回值&#xff0c;参数为…

uboot ,s5pv210 ,bootm分析

先来看看 bootm 的逻辑。 1、 首先是 两 zimage 加上一个头, 变成 Uimage 2、然后是将 uimage 烧写到 TF 卡上去。 3、 然后是 TF 卡上的 uimgae 拷贝到 内存的一段位置上。 4、 然后就是 跳转到 内存的 这个位置上 去运行代码了。 uboot中 将 zimage 变成 uimage…

JS基础 -- 数组 (对象 / 数组 / 类数组 / 对象数组)的遍历

一、数组&#xff1a; 数组是复杂数据类型&#xff0c;用于存储一组有序的数据。 1、创建数组&#xff1a; ① 使用 new 关键字&#xff1a; let arr new Array() // 创建一个长度为0的空数组 let arrLength new Array(5) // 创建一个长度为5的空数组② 字面量形式&#…

利用 AI 高效生成思维导图的简单实用方法

#工作记录 适用于不支持直接生成思维导图的AI工具&#xff1b;适用于AI生成后不能再次编辑的思维导图。 在日常的学习、工作以及知识整理过程中&#xff0c;思维导图是一种非常实用的工具&#xff0c;能够帮助我们清晰地梳理思路、归纳要点。而借助 AI 的强大能力&#xff0c…

嵌入式学习(21)-正点原子脱机下载器Mini-Pro的使用

一、概述 通过脱机下载器可以脱离电脑给电路板下载程序&#xff0c;方便在产线上给PCB烧录程序。 二、程序烧录到脱机下载器 1、驱动及软件下载&#xff1a; https://download.csdn.net/download/A18763139629/90215719 2、软件安装 3、烧录程序 通过USB线与脱机下载器连…

二维码文件在线管理系统-收费版

需求背景 如果大家想要在网上管理自己的文件&#xff0c;而且需要生成二维码&#xff0c;下面推荐【草料二维码】&#xff0c;这个系统很好。特别适合那些制造业&#xff0c;实体业的使用手册&#xff0c;你可以生成一个二维码&#xff0c;贴在设备上&#xff0c;然后这个二维码…

【C语言程序设计——循环程序设计】枚举法换硬币(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 一、循环控制 / 跳转语句的使用 1. 循环控制语句&#xff08;for 循环&#xff09; 2. 循环控制语句&#xff08;while 循环&#xff09; 3. 跳转语句&#xff08;break 语句&#xff09; 4. 跳转语句&#xff08;continue 语句&…

【Multisim用74ls92和90做六十进制】2022-6-12

缘由Multisim如何用74ls92和90做六十进制-其他-CSDN问答 74LS92、74LS90参考

计算机的错误计算(二百)

摘要 用三个大模型计算 exp(123.456). 结果保留10位有效数字。三个大模型的输出均是错误的&#xff0c;虽然其中一个给出了正确的 Python代码。 例1. 计算 exp(123.456). 保留10位有效数字。 下面是与第一个大模型的对话。 以上为与一个大模型的对话。 下面是与另外一个大模…

自行下载foremos命令

文章目录 问题描述其他小伙伴的成功解决方案&#xff0c;但对我不适用解决思路失败告终 最终解决成功解决思路解决步骤 问题描述 在kali系统终端中输入foremost&#xff0c;显示无此命令 其他小伙伴的成功解决方案&#xff0c;但对我不适用 解决思路 正常来说使用命令 apt-g…

docker 安装influxdb

docker pull influxdb mkdir -p /root/influxdb/data docker run -d --name influxdb -p 8086:8086 -v /root/influxdb/data:/var/lib/influxdb influxdb:latest#浏览器登录&#xff1a;http://192.168.31.135:8086&#xff0c;首次登录设置用户名密码&#xff1a;admin/admin1…

Leetcode打卡:我的日程安排表II

执行结果&#xff1a;通过 题目 731 我的日程安排表II 实现一个程序来存放你的日程安排。如果要添加的时间内不会导致三重预订时&#xff0c;则可以存储这个新的日程安排。 当三个日程安排有一些时间上的交叉时&#xff08;例如三个日程安排都在同一时间内&#xff09;&#…

创龙3588——debian根文件系统制作

文章目录 build.sh debian 执行流程build.sh源码流程 30-rootfs.sh源码流程 mk-rootfs-bullseys.sh源码流程 mk-sysroot.sh源码流程 mk-image.sh源码流程 post-build.sh 大致流程系统制作步骤 build.sh debian 执行流程 build.sh 源码 run_hooks() {DIR"$1"shiftf…

拟声 0.60.0 | 拟态风格音乐播放器,支持B站音乐免费播放

「拟声」是一款音乐播放器&#xff0c;不仅支持音视频的本地播放&#xff0c;还提供了账号注册功能&#xff0c;登录后可享受自动同步歌单、歌词等。它支持播放绝大多数音频格式&#xff0c;具备固定输出采样率、独占输出、内置均衡器和音调调整等功能。同时&#xff0c;它也支…

计算机网络 (16)数字链路层的几个共同问题

一、封装成帧 封装成帧是数据链路层的一个基本问题。数据链路层把网络层交下来的数据构成帧发送到链路上&#xff0c;以及把接收到的帧中的数据取出并上交给网络层。封装成帧就是在一段数据的前后分别添加首部和尾部&#xff0c;构成了一个帧。接收端在收到物理层上交的比特流后…

Linux Shell 脚本编程基础知识篇—awk的条件判断(3)

ℹ️大家好&#xff0c;我是练小杰&#xff0c;今天周五了&#xff0c;又是一周过去了&#x1f606; 本文是有关Linux shell脚本编程的awk命令的条件语句&#xff0c;后续我会不断增加相关内容 ~~ 回顾:【awk字符串函数和内置变量】 更多Linux 相关内容请点击&#x1f449;【Li…

小程序学习07—— uniapp组件通信props和$emit和插槽语法

目录 一 父组件向子组件传递消息 1.1 props &#xff08;a&#xff09;传递静态或动态的 Prop &#xff08;b&#xff09;单向数据流 二 子组件通知父组件 2.1 $emit &#xff08;a&#xff09;定义自定义事件 &#xff08;b&#xff09;绑定自定义事件 三 插槽语法…

【深度学习进阶】基于CNN的猫狗图片分类项目

介绍 基于卷积神经网络&#xff08;CNN&#xff09;的猫狗图片分类项目是机器学习领域中的一种常见任务&#xff0c;它涉及图像处理和深度学习技术。以下是该项目的技术点和流程介绍&#xff1a; 技术点 卷积神经网络 (CNN): CNN 是一种专门用于处理具有类似网格结构的数据的…

【pytorch-lightning】架构一览

pytorch-lightning是基于pytorch的一个套壳项目&#xff0c;适配pytorch的版本同步更新速度很快。 它将训练的几个主要流程模块化&#xff0c;减少重复工作&#xff0c;同时让支持分布式训练&#xff0c;不同平台的训练迁移变得更加简单。 官网链接