ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

数据结构C语言课设:运动会分数统计系统的设计与实现

数据结构C语言课设:运动会分数统计系统的设计与实现 简介一份面向数据结构课程设计的完整《运动会分数统计》报告文档适合计算机相关专业学生参考尤其适合需要完成类似信息管理系统课程设计、掌握排序查找等知识点的读者。资源包内仅有1个doc文件大小916KB既包含课程设计报告正文也附有源代码说明目前已有三千三百八十七人学习下载。文档围绕运动会分数统计系统展开先进行需求分析再给出概要设计与详细设计采用结构体数组保存项目及学校数据利用文件读写函数实现持久化存储使用冒泡排序实现按学校编号、总分、男女团体总分输出并通过顺序表查询学校项目情况或按项目编号获取前三名或前五名。此外还配有输入输出、统计函数的设计思路以及合法、非法、局部非法数据的测试结果。全文从需求到实现再到调试总结体系完整可作为课程设计报告撰写和程序实现的直接参考。1. 运动会分数统计是数据结构课设里的「中线题」需求小但考察面全运动会分数统计是数据结构课程设计里最经典的题型之一录入各院系运动员在各个项目上的名次按名次换算成分数汇总出男团、女团和院系总分排行再提供按项目和按院系的查询。题目不复杂但正好把线性表、结构体、排序算法和文件持久化全部串起来覆盖数据结构 c 语言版教材里最常考的几个章节所以历届选题中它始终排在前几名。网上流传的版本不少是 .doc 格式的课程设计报告加源代码附件报告里写需求分析、数据结构定义、流程图和测试用例代码则是完整的 C 语言工程。我不打算在这里复述某一份现成文档而是按自己做这套题目的思路讲清楚数据怎么组织、排序怎么实现、文件怎么存、验收时哪些坑会被老师反复问。适合正在选课设题目或者代码写了一半被指针和排序卡住的同学。2. 数据怎么存结构体数组比链表更合适字段设计决定后面编码省不省事运动会分数统计的数据量一般就几十个院系、十几个项目属于典型的小规模数据。选型时不用刻意追求复杂关键是访问方式。需求里有三类高频操作按院系编号改成绩、按总成绩排序、按项目查名次。院系总数固定且编号唯一增删频率极低最常见的操作是随机访问和排序所以顺序表结构体数组天然占优。链表写起来好看但排序和随机访问都要遍历代码量翻倍课设验收时反而更容易暴露指针问题。这里的经验是数据结构没有绝对好坏贴合访问模式才是选型标准。2.1 两张核心表院系信息表和成绩表先定字段再写代码第一张表存院系基本信息第二张表存每个项目的成绩记录。很多翻车现场都是字段没想清楚就动手写写到统计总分时发现缺了性别字段又回去改结构体白白浪费时间。我用的是两个结构体#define MAX_DEPT 30 // 最多参赛院系数 #define MAX_EVENT 50 // 最多比赛项目数 typedef struct { int id; // 院系编号从 1 开始 char name[24]; // 院系名称 int male_score; // 男团总分 int female_score; // 女团总分 int total_score; // 团体总分 } DeptInfo; typedef struct { int event_id; // 项目编号 char event_name[32];// 项目名称比如 男子100米 int rank; // 该项目取得的团体名次 int dept_id; // 取得该名次的院系编号 int score; // 按名次换算后的分数 int is_male; // 1 表示男子项目0 表示女子项目 } ScoreItem;DeptInfo 对应院系表ScoreItem 对应成绩明细表。ScoreItem 里记 dept_id 而不是直接记院系名称是为了避免名称冗余统计总分时用 dept_id 去关联院系表。这是典型的关系型思维放在 C 语言结构体里同样适用后续无论做排序还是做查询都能少写一半字符串比较代码。两个宏 MAX_DEPT 和 MAX_EVENT 是后续所有数组下标的边界。改数据规模时只需要改这两行不需要动任何循环逻辑这是课设验收时可以主动讲的细节。另一个容易忽略的点是 ScoreItem 里把「名次」和「得分」分成两个字段而不是只存得分。这样既能按名次输出名次表也能在数据校验时反查分数算得对不对。如果把换算后的分数直接覆盖到 rank 上后面想改计分规则就得重录数据等于给自己断了后路。2.2 为什么坚持用顺序表访问模式决定选型而不是「显得高级」我见过不少参考源代码用链表实现院系信息管理优点是看起来可以动态扩容。但对这份题目来说院系数通常在程序运行前就能确定由用户先输入参赛院系数 N。既然 N 已知动态扩展就失去了意义顺序表在内存连续性和访问效率上都更好。排序环节直接决定最终排名输出。用结构体数组时qsort 或手写排序都只需要交换结构体变量一行赋值语句就能完成两个元素交换换成单链表交换节点还是交换数据域都会引入额外的指针处理。老师验收时最常问的就是「为什么用顺序表不用链表」能回答出「数据规模固定、随机访问频繁、排序成本低」这三点基本就过关了。反过来如果题目明确要求院系数量在运行时动态增减我才会考虑链表方案这是由题目的访问模式决定的不是越复杂的结构越加分。顺序表另一个好处是文件持久化简单。fwrite 可以直接按结构体大小写一整块内存读回来也一样链表则需要逐个节点序列化。第 4 章会专门讲文件部分这里先记住结论这份题目用结构体数组代码量最少逻辑最不容易出错。很多同学把课设做成「为了用链表而用链表」结果排序环节成了全场 bug 最多的地方完全没必要。2.3 最小可运行的院系录入代码先让系统跑起来再往上加功能拿到题目先别急着写完所有功能先把「录入 N 个院系 → 打印列表」这条主链路跑通。这一步能验证结构体和输入输出逻辑没问题后面所有功能都基于此扩展。下面这段代码可以直接复制到你的源代码文件里作为骨架。#include stdio.h #include string.h #define MAX_DEPT 30 typedef struct { int id; char name[24]; int male_score; int female_score; int total_score; } DeptInfo; int main() { DeptInfo depts[MAX_DEPT]; int n, i; printf(请输入参赛院系数: ); while (scanf(%d, n) ! 1 || n 0 || n MAX_DEPT) { printf(院系数需在 1 到 %d 之间请重新输入: , MAX_DEPT); while (getchar() ! \n); // 清空输入缓冲防止残留字符影响下次读取 } for (i 0; i n; i) { depts[i].id i 1; printf(请输入第 %d 个院系名称: , i 1); scanf(%23s, depts[i].name); // %23s 防止超长输入覆盖结构体其他字段 depts[i].male_score 0; depts[i].female_score 0; depts[i].total_score 0; } printf(录入完成当前院系列表:\n); for (i 0; i n; i) { printf(%d %s\n, depts[i].id, depts[i].name); } return 0; }这段代码里有两个参数值得说明。scanf(%d, n) 的返回值判断让非数字输入不会被静默接受配合内层 while (getchar() ! \n) 清空缓冲避免下一次 scanf 读到上次输入的残留字符。scanf(%23s, name) 中的 23 是字符数组长度 24 减 1防止用户超长输入时把名称尾部覆盖到结构体后面的内存这是 C 语言课堂不常讲但实际开发必须处理的边界。主循环里先把三个分数字段全部初始化为 0也是个容易忽略的步骤。结构体数组声明后是栈上的随机值不初始化的话后面统计时会把垃圾值累加进去表现成「总分莫名其妙特别大」。初始化放在录入院系时一次做完比统计函数里再补判空省事得多。这段骨架跑通后再把成绩录入、统计、查询、文件保存几个模块按菜单填进去整个工程的结构就很清晰了。3. 名次转分数与总分排序把计分规则做成参数排序算法两种写法都给计分规则是这道题的核心业务逻辑也是最容易被需求描述坑到的地方。常见的计分表有两种一种取前五名按 7、5、3、2、1 计分另一种取前八名按 9、7、6、5、4、3、2、1 计分。题目可能还会加限制条件比如同一院系在一个项目里最多取两人成绩或者按报名表预先确定参赛人数。把这些规则做成可配置的全局数组和参数而不是硬编码在业务逻辑里是让代码具备可维护性的关键。这样无论规则怎么变改的只是数据不是流程。3.1 计分表设计前几名、各名次分值、性别分组三个参数分开配我习惯把计分表定义成全局常量数组并在文件头部注释说明规则来源。这样答辩时评委问「如果规则改成取前六名怎么改」你只需要改一处数组长度和内容运行时不用重新编译逻辑代码。// 计分规则取前 5 名分值依次为 7, 5, 3, 2, 1 #define RANK_LIMIT 5 const int score_table[RANK_LIMIT] {7, 5, 3, 2, 1}; // 根据名次得到分数第 6 名及以后不得分 int score_for_rank(int rank) { if (rank 1 rank RANK_LIMIT) { return score_table[rank - 1]; } return 0; }score_table[rank - 1] 用名次做下标是因为名次从 1 开始而数组下标从 0 开始。这个「名次减一」的映射几乎出现在每一份参考源代码里同时也是新手高频错误写成 score_table[rank] 就会数组越界取到相邻内存的垃圾值。score_for_rank 开头的越界判断必须保留因为录入环节可能产生非法名次查询和统计环节也可能会拿脏数据来算分。这里值得多提一句计分规则要改成参数而不是深埋在统计函数里。有的同学在算分时直接写 if (rank 1) score 7; else if (rank 2) score 5; 功能上没错但换个计分表就要改一长串分支而且每个分支都可能漏改。用数组做映射规则变化只动 score_table。这种「数据驱动」的小习惯在后面对 ScoreItem 做批量换算时会非常省心也更接近真实项目里配置项分离的做法。3.2 总分聚合与排序手写冒泡是基本功qsort 是工程写法两个都得会聚合阶段的逻辑是遍历 ScoreItem 数组根据 is_male 字段把 score 累加到对应院系的 male_score 或 female_score最后把两者相加得到 total_score。代码本身不复杂但要注意按 dept_id 定位院系时最好用下标直接索引前提是院系编号与数组下标严格对应即 1 号院系存在 depts[0] 这个约定。void aggregate(DeptInfo *depts, int dept_n, ScoreItem *items, int item_n) { int i, d; // 先清零避免重复调用时分数叠加 for (i 0; i dept_n; i) { depts[i].male_score 0; depts[i].female_score 0; depts[i].total_score 0; } for (i 0; i item_n; i) { d items[i].dept_id - 1; // 院系编号转数组下标 if (d 0 || d dept_n) { printf(成绩记录 %d 的院系编号 %d 非法已跳过\n, i, items[i].dept_id); continue; } if (items[i].is_male) { depts[d].male_score items[i].score; } else { depts[d].female_score items[i].score; } depts[d].total_score items[i].score; } }注意 aggregate 函数开头先统一清零。如果没有这一步用户在菜单里连续触发两次统计分数就会翻倍。这也是数据结构和算法题目里「可重入」思想的体现同一个函数被调用多次结果应该一致不能依赖数组初始状态。还有一个细节是 items[i].dept_id - 1 之后立刻做边界检查一旦成绩文件里混入不存在的院系编号程序会打印提示并跳过而不是直接越界崩溃。排序这里我写两种答辩时老师大概率会问「你会几种排序」。手写冒泡排序考察基本功qsort 体现工程能力#include stdlib.h // 手写冒泡按 total_score 降序排列 void bubble_sort_dept(DeptInfo *depts, int n) { int i, j; DeptInfo tmp; for (i 0; i n - 1; i) { for (j 0; j n - 1 - i; j) { if (depts[j].total_score depts[j 1].total_score) { tmp depts[j]; depts[j] depts[j 1]; depts[j 1] tmp; } } } } // qsort 比较函数总分降序总分相同时院系编号升序 int cmp_dept(const void *a, const void *b) { const DeptInfo *pa (const DeptInfo *)a; const DeptInfo *pb (const DeptInfo *)b; if (pa-total_score ! pb-total_score) { return pb-total_score - pa-total_score; } return pa-id - pb-id; }冒泡排序的时间复杂度是 O(n^2)对几十个院系完全够用。内层循环的 j n - 1 - i 是标准写法每一轮把当前未排序列的最小值沉到末尾整体呈降序输出。qsort 版的比较函数返回值语义是返回值小于 0 时a 排在 b 前面。这里刻意处理了总分相同的情况返回 pa-id - pb-id让编号小的排前面。这能避免一个常见问题总分相同时两次运行排名可能会不同看起来像随机数玄学。3.3 查询功能按项目查名次、按院系查明细遍历数组就是全部逻辑查询功能通常有两个入口输入项目编号输出该项目全部名次及对应院系输入院系编号输出该院系所有获奖项目和得分。两个功能本质都是对 ScoreItem 数组做一次遍历找到匹配记录逐条打印。void query_by_event(int event_id, ScoreItem *items, int item_n) { int i, found 0; printf(项目 %d 名次列表:\n, event_id); for (i 0; i item_n; i) { if (items[i].event_id event_id) { printf( 第 %d 名: 院系 %d, 得分 %d\n, items[i].rank, items[i].dept_id, items[i].score); found 1; } } if (!found) { printf( 未找到该项目成绩\n); } }这个函数逻辑很直白两点值得说。第一遍历找到所有匹配记录而不是找到一条就返回因为一个项目下有多条名次记录。第二found 标志位用来区分「查无结果」和「查询成功」而不是靠 printf 有没有输出判断。很多同学的代码在查询无结果时不提示任何信息演示时输入一个不存在的项目号界面毫无反应评委只会觉得程序坏了。按院系查询的写法完全对称只是把判断条件改成 dept_id 相等。两个查询函数加起来不到四十行却是菜单里被点得最频繁的功能值得把它们单独拆成函数而不是揉在主函数里。4. 文件存取与数据校验二进制还是文本先想清楚谁来读这个文件课设要求里通常有一条「将数据保存在文件中」这一条看似简单实际上决定了程序演示时的体验。如果每次运行都要重新录入几十条成绩演示现场会非常尴尬。文件模块要解决的核心问题有两个用什么格式存储以及读取时如何校验数据完整性。下面的方案都基于 C 标准库不需要引入任何第三方依赖在任何支持 C89 的编译器上都能直接编译运行。4.1 格式选型文本可读性好、便于改错二进制省心、但不适合跨机器交换常见做法是存文本文件因为老师可能会打开数据文件检查内容。用 fprintf 按行写每个字段用空格分隔读回时用 fscanf 按同样顺序解析。好处是人眼能读懂如果数据文件被手工改错也能对照着修正坏处是解析时对空格和换行敏感容易出现「多了一个空格就读错数据」的情况。另一种是二进制文件fwrite 和 fread 直接读写结构体内存块。好处是读写代码极短性能也高坏处是文件不可读更不能保证换一个编译器或机器后还能正确打开因为不同平台对结构体字节对齐的处理不同sizeof(DeptInfo) 可能不一样。我自己在这份课设里优先用文本格式理由只有一个课设需要展示数据文件可读能让老师一眼看懂程序到底存了什么。真到了几万条成绩的性能场景才值得用二进制。void save_to_file(DeptInfo *depts, int dept_n, ScoreItem *items, int item_n, const char *path) { FILE *fp fopen(path, w); if (fp NULL) { perror(打开文件失败); return; } fprintf(fp, DEPT %d\n, dept_n); for (int i 0; i dept_n; i) { fprintf(fp, %d %s %d %d %d\n, depts[i].id, depts[i].name, depts[i].male_score, depts[i].female_score, depts[i].total_score); } fprintf(fp, ITEM %d\n, item_n); for (int i 0; i item_n; i) { fprintf(fp, %d %s %d %d %d %d\n, items[i].event_id, items[i].event_name, items[i].rank, items[i].dept_id, items[i].score, items[i].is_male); } fclose(fp); }这一段有两个习惯值得学。第一用 DEPT 和 ITEM 前缀充当段标记读取时先读段标记再决定后面解析多少行这让文件格式有自描述能力以后加新字段不用破坏旧文件的读取逻辑。第二每个 fprintf 都写完整一整行字符串字段放在中间位置。注意院系名和项目名里不能有空格否则按空格分隔的解析会错位所以录入时我限制了名称只能是连续字符或者用下划线连接。读取代码比写入更考验耐心因为文件可能被手工编辑过也可能不完整。load 函数必须对每一段做标记校验和数量校验void load_from_file(DeptInfo *depts, int *dept_n, ScoreItem *items, int *item_n, const char *path) { FILE *fp fopen(path, r); if (fp NULL) { perror(打开文件失败); return; } char tag[16]; int n 0; fscanf(fp, %15s %d, tag, n); if (strcmp(tag, DEPT) ! 0 || n 0 || n MAX_DEPT) { printf(文件格式错误: 院系段标记不对或数量越界\n); fclose(fp); return; } *dept_n n; for (int i 0; i n; i) { fscanf(fp, %d %23s %d %d %d, depts[i].id, depts[i].name, depts[i].male_score, depts[i].female_score, depts[i].total_score); } fscanf(fp, %15s %d, tag, n); if (strcmp(tag, ITEM) ! 0 || n 0 || n MAX_EVENT) { printf(文件格式错误: 成绩段标记不对或数量越界\n); fclose(fp); return; } *item_n n; for (int i 0; i n; i) { fscanf(fp, %d %31s %d %d %d %d, items[i].event_id, items[i].event_name, items[i].rank, items[i].dept_id, items[i].score, items[i].is_male); } fclose(fp); }这段代码里的 %15s、%23s、%31s 限长格式符作用与 scanf 场景相同防止从文件读入超长字符串时溢出缓冲区。每次 fscanf 后应该检查返回值是否等于期望的字段数如果文件被截断或混入不可见字符fscanf 会返回 EOF 或更少的匹配数程序应当立刻报错而不是继续用垃圾数据往下算。这是文件模块里最重要的防御式编程习惯能挡掉一半以上的演示事故。4.2 数据校验数量越界、编号不存在、计分超过上限读入时一次查完文件读入后不要直接开始排序先跑一遍完整校验。我把校验逻辑放在 load 之后、主菜单之前所有非法数据打印到屏幕提示用户手工修正文件或重新录入。三个必查项是院系编号是否落在合法范围、成绩记录里的 dept_id 是否有对应院系、score 是否大于计分表里的最高分。下面这段校验函数是关键片段。int validate_data(DeptInfo *depts, int dept_n, ScoreItem *items, int item_n) { int i, errors 0; for (i 0; i item_n; i) { if (items[i].dept_id 1 || items[i].dept_id dept_n) { printf(第 %d 条成绩: 院系编号 %d 不存在\n, i, items[i].dept_id); errors; } if (items[i].score 0 || items[i].score score_table[0]) { printf(第 %d 条成绩: 得分 %d 不在合法范围(0~%d)\n, i, items[i].score, score_table[0]); errors; } if (items[i].rank 0) { printf(第 %d 条成绩: 名次 %d 非法\n, i, items[i].rank); errors; } } return errors; }注意这里 score score_table[0] 的检查用 score_table[0] 而不是写死 7因为计分表的第一项永远是最高分规则改动时校验代码不用跟着改。这是把规则参数化的另一个好处规则、统计、校验都复用了同一份数据源不会出现一边改了一边没改的情况。文件模块最容易被忽略的是「读入后内存里的数据是否可信」这个问题。不校验直接进入查询和统计遇到脏数据时程序可能输出完全不合理的排名评委的第一反应是「代码错了」而不是「数据错了」。提前写一个 validate_data在程序入口调用并检查返回值能避免演示时当场翻车。5. 避坑清单课设演示最容易翻车的五个问题从现象查到根因这一章的五条坑都是我实际改代码时反复遇到过的每一条都按「现象 → 原因 → 解决」的思路写。第 5 章就当质检清单用每改完一段代码对照着查一遍。5.1 项目录完一排序院系编号和总分全对不上最常见的是录入顺序是 1 号、2 号、3 号院系排序输出后编号顺序变成 3、1、2而且每个编号后面的总分跟排序前完全对不上。原因出在排序时交换了整个结构体数组排序后 depts[i] 里的 id 不再等于 i 1后续统计功能如果直接用下标 depts[i].id 去访问自然就找错对象。解决办法是让所有访问都走 id 字段而不是依赖下标。统计总分时通过 depts 数组查找对应 id 的院系排序时也只比较 total_score交换的是整个结构体不改变 id 属性。如果代码里还有大量 depts[i].id i 1 的假设要么在录入阶段保证 id 与下标一致要么把所有假设改成显式比较否则排序功能一上线就会爆发连锁错误。5.2 总分相同两次排序结果不一样现象是 A 院系和 B 院系总分都是 15 分第一次运行 A 排在前面第二次运行 B 排在前面。原因很简单冒泡排序在总分相等时不做任何处理交换与否取决于数组原始排列这不是排序算法本身的缺陷而是缺少明确的次级排序键。解决方式是在比较逻辑里加入编号作为第二排序条件总分相同时编号小者在前。qsort 版里就是 cmp_dept 最后的 return pa-id - pb-id。这看起来是小细节但评委如果连续按两次排名菜单发现排名变了会直接质疑整个排序算法的正确性所以省掉这行代码非常不值得。5.3 fscanf 读文件时字符串和数字错位现象是成绩文件加载后院系名变成了一串数字或者某个项目的名称变成了上一个项目的名称。原因几乎都是保存和读取的格式串不一致fprintf 写出去的字段顺序是「编号 名称 分数」读取时如果漏写了 %s 或者把顺序写反字符串就会被当作数字解析后面的字段全部错位。解决方式是让 save 和 load 共用同一份格式注释一个字段一个字段对齐检查。我的土办法是在两个函数上方各写一行注释注明「写入顺序id name male female total」然后逐字段对照。更省心的是每行只存一条完整记录字符串一律用 %s 或限长 %31s 读取不让字符串包含空格从源头消除分隔歧义。5.4 输入名次 9算出个离谱分数现象是老师随手输入一个名次 9程序不报错最后该院系的成绩单上出现一个明显不对的分数。原因在于 score_for_rank 函数只校验了 rank 是否在 1 到 RANK_LIMIT 之间如果调用方传入了负数或超大值score_table[rank - 1] 就会数组越界读到的可能是相邻内存里的随机值表现为正分、负分或者很大的数。解决方法是把越界检查写三层录入成绩时用 while 循环限制名次范围score_for_rank 入口处对 rank 0 或 rank RANK_LIMIT 直接返回 0文件校验时再对 rank 字段做一次合法性检查。越界检查写三遍都不嫌多因为每一层都可能放过不同的非法输入来源。5.5 数组越界但不崩程序跑完结果全错现象是程序运行不报错但输出里出现一个「幽灵院系」分数巨大或者项目列表里多出一条不存在记录。原因是 C 语言数组越界是未定义行为不一定会触发段错误更常见的是把相邻内存的数据读出来。MAX_DEPT 定义为 30录入 31 个院系时第 31 个结构体就写到了相邻内存上后续所有读取都可能在错误数据上运算。解决方式是在所有循环里坚持用 i n 并且先校验 n 的范围录入院系数时用 while 循环强制 n 不超过 MAX_DEPT。还有一个血泪经验可以在 depts 数组声明为 depts[MAX_DEPT 1]并手动把下标 30 的位置填成 id 0 的哨兵一旦有越界写入检查哨兵被改动就能立刻定位问题。越界不报错的 bug比直接崩溃难查十倍宁可多写几行防御代码。6. 答辩前自检用三个边界场景验证你的源代码顺便准备两个加分回答课设评分不只看功能是否完整还看你对代码的熟悉程度和边界处理习惯。我自己答辩前会准备一个临时测试文件把三个边界场景各跑一遍。第一录入 1 个院系和正好 30 个院系确认极端数量下不会越界第二录入一个项目 30 人参赛并只取前 5 名计分确认 score_for_rank 对第 6 名返回 0后面名次不会影响总分第三手工编辑数据文件把某个成绩记录的院系编号改成 99确认 validate_data 能报错提示而不是让程序崩溃。这三个场景的输出结果也正好可以作为数据结构实验报告里的测试用例部分比空写「系统测试通过」有说服力得多。有两个加分回答值得提前准备。第一个是「为什么用结构体数组而不用链表」围绕数据规模固定、随机访问多、排序成本低这三点展开第二个是「如果参赛院系数量不固定你的系统怎么扩展」可以答改成动态数组或链表并指出需要同步修改统计、排序、文件读写三个模块。这两个问题答得顺比现场多写一百行代码更能得分。我在自己做的版本里还给排名功能加了一个小特性总分相同的院系自动显示「并列第 X 名」实现只要在排序后比较相邻两个院系的总分代码不到 20 行但演示效果很有记忆点。最后分享一条我做课设攒下的教训别把源代码和实验报告分开写先让报告里的数据结构定义和代码里的结构体保持一致再动手写功能。因为报告往往是代码的老祖宗代码改完报告没改答辩时老师看着两张不一致的表提问场面会很尴尬。把这一条习惯保持到后续的课程设计和毕业设计里能省下大量返工时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表