资讯动态

运动会分数统计实验报告:结构体与线性表实现

发布时间:2026/10/2 7:24:20 来源:尧图企业网站定制
简介这份运动会分数统计实验报告面向数据结构与软件设计课程的初学者及需要完成课程设计的学生围绕线性链表、结构体与模块化函数设计解决多学院、多项目比赛成绩录入与团体总分统计的实际问题。压缩包内共1个docx文件约91KB内容涵盖实验目的、要求、数据定义、函数设计、测试结果、调试分析与完整C源代码可直接作为实验报告模板或参考范例。报告以n4、m3、w2为测试用例详细讲解InitSCH初始化、input输入、mark算分、output输出四个核心函数的实现思路并给出指针操作、性别校验、递归重输等健壮性处理经验。目前已有390人学习下载适合希望掌握链表应用、条件判断、循环结构与错误处理机制的读者对照练习也可用于快速梳理实验报告写作框架与排错思路。1. 运动会分数统计实验报告从结构体到线性表的落地拆解校运会三天七个学院十四个比赛项目手工统计分数时最容易翻车的不是算错加法而是同一名运动员同时报了田赛和径赛名次分和破纪录加分叠在一起最后总分对不上。运动会分数统计实验报告.docx 这个标题背后本质是一个典型的「数据结构课程设计」任务用结构体描述学校、项目、运动员和名次用线性表组织这些数据再通过输入输出和分支结构完成分数录入、查询和汇总。它适合正在学 C 语言数据结构、需要交实验报告或课程设计的学生也适合想用一个小项目把结构体、链表、文件读写串起来的自学者。热搜里反复出现的「数据结构」「结构体」「线性表」「输入输出」「分支结构」恰好就是这个实验的五个核心零件。下面按我实际带学生做这个题目的顺序把选型理由、代码骨架、参数设置和踩坑记录一次讲透。2. 先定数据模型结构体怎么拆才不返工2.1 为什么用结构体而不是并行数组很多同学第一反应是开几个数组int schoolId[100]、int score[100]、char name[100][20]。这种写法在数据量小的时候能跑但一旦要按学校汇总、按项目筛选、按运动员查名次下标就会对不上。结构体的价值在于把「一个运动员的一次成绩」绑成一个整体移动、排序、写文件都以这个整体为单位不会出现「改了分数忘了改名次」的玄学问题。运动会分数统计里我一般拆成三层结构体学校、项目、成绩记录。学校存校名和总分项目存项目编号、名称、类型田赛/径赛、取前几名成绩记录存学校编号、运动员编号、项目编号、名次、得分、是否破纪录。这样拆的好处是汇总总分时只需要遍历成绩记录按学校编号累加不用回头改学校结构体里的冗余字段。/* 运动会分数统计的三层结构体定义 */ #define MAX_SCHOOL 20 #define MAX_ITEM 30 #define MAX_RECORD 500 typedef struct { int id; /* 学校编号从1开始 */ char name[30]; /* 学校名称 */ int total; /* 总分汇总时重算 */ } School; typedef struct { int id; /* 项目编号 */ char name[30]; /* 项目名称 */ int type; /* 1田赛, 2径赛 */ int topN; /* 取前几名通常5或8 */ } Item; typedef struct { int schoolId; /* 所属学校 */ int itemId; /* 参加项目 */ int athleteNo; /* 运动员编号 */ int rank; /* 名次0表示未获奖 */ int score; /* 名次分 */ int bonus; /* 破纪录加分 */ } Record;参数说明MAX_RECORD按「学校数 × 项目数 × 每校每项最多3人」估算20×30×31800取500是保守值实际用动态数组或链表更稳。topN决定计分范围常见规则是前5名计 5、4、3、2、1 分前8名计 9、7、6、5、4、3、2、1 分这个值必须和秩序册一致否则汇总全错。bonus单独存而不是直接加进score是为了后面能区分「名次分」和「破纪录加分」实验报告里通常要求分别列出。2.2 线性表选顺序表还是链表热搜里「数据结构链表」「c结构体链表基本语法」出现频率很高但运动会分数统计这个场景我建议先用顺序表把逻辑跑通再考虑链表。原因很直接数据量可控几百到几千条主要操作是遍历汇总和按条件查询顺序表的随机访问反而更快代码也短。链表的优势在频繁插入删除而这个实验里录入阶段是追加查询阶段是遍历插入删除很少。顺序表的核心就是一个数组加一个长度计数器typedef struct { Record data[MAX_RECORD]; int length; } RecordList; /* 追加一条成绩记录返回新的长度 */ int appendRecord(RecordList *list, Record rec) { if (list-length MAX_RECORD) { return -1; /* 表满调用方需要处理 */ } list-data[list-length] rec; list-length; return list-length; }逻辑说明appendRecord只做追加不做去重和校验校验放在录入函数里做职责分离。参数list用指针是为了让函数内修改对调用方可见这是 C 语言里最容易忘的细节。如果实验报告要求用链表把RecordList换成带头结点的单链表即可appendRecord改成尾插其余汇总逻辑几乎不用动这也是先定结构体再选容器的好处。3. 输入输出与分支结构录入、计分、查询的最小闭环3.1 录入函数怎么写才不会被非法输入卡死热搜里「c 结构体」「fscanf结构体」「python输入输出」都指向同一个痛点输入格式和校验。运动会现场录入最怕两件事一是名次填了 0 或负数二是同一项目同一学校重复录入。我的做法是在录入函数里做三层校验范围校验、重复校验、项目存在性校验。/* 录入一条成绩成功返回0失败返回错误码 */ int inputRecord(RecordList *list, School schools[], int schoolCnt, Item items[], int itemCnt) { Record rec; printf(请输入: 学校编号 项目编号 运动员编号 名次 是否破纪录(0/1)\n); if (scanf(%d %d %d %d %d, rec.schoolId, rec.itemId, rec.athleteNo, rec.rank, rec.bonus) ! 5) { while (getchar() ! \n); /* 清空错误输入防止死循环 */ return -1; } if (rec.schoolId 1 || rec.schoolId schoolCnt) return -2; if (rec.itemId 1 || rec.itemId itemCnt) return -3; if (rec.rank 0 || rec.rank 8) return -4; /* 重复校验同项目同学校同运动员只能有一条 */ for (int i 0; i list-length; i) { if (list-data[i].itemId rec.itemId list-data[i].schoolId rec.schoolId list-data[i].athleteNo rec.athleteNo) { return -5; } } rec.score calcScore(rec.rank, items[rec.itemId - 1].topN); return appendRecord(list, rec) 0 ? -6 : 0; }逻辑说明scanf返回值不等于 5 说明输入格式不对必须清空缓冲区否则下一次循环会继续读同一个坏字符程序看起来像卡死。重复校验用三重条件是因为同一个运动员可能参加多个项目不能只按运动员编号去重。calcScore单独抽出来方便后面改计分规则时只动一个函数。参数说明topN从项目结构体里取保证不同项目可以有不同的取奖范围。rank为 0 表示参赛未获奖计 0 分这是合法输入不要当成错误。bonus用 0/1 表示是否破纪录破纪录加分值建议单独定义常量比如#define BONUS 5不要硬编码在函数里。3.2 计分规则用分支结构还是查表热搜里「分支结构」「建立gitee分支结构等的指令」虽然语境不同但都指向「条件判断」这个动作。计分规则天然适合用switch或查表。前8名计分如果用switch代码长但直观用数组查表更短但要注意名次越界。/* 名次分查表下标0不用1~8对应名次 */ static const int SCORE_TABLE_8[9] {0, 9, 7, 6, 5, 4, 3, 2, 1}; static const int SCORE_TABLE_5[6] {0, 5, 4, 3, 2, 1}; int calcScore(int rank, int topN) { if (rank 0) return 0; /* 未获奖 */ if (topN 8 rank 8) return SCORE_TABLE_8[rank]; if (topN 5 rank 5) return SCORE_TABLE_5[rank]; return 0; /* 名次超出取奖范围 */ }逻辑说明查表比switch少写很多行而且改规则时只改数组。rank 0的分支必须放在最前面否则SCORE_TABLE_8[0]会返回 0看起来对但语义上是「未获奖」而不是「第0名」实验报告里要区分。topN只支持 5 和 8 两种常见规则如果秩序册是前6名直接加一个SCORE_TABLE_6即可不要用复杂的公式硬算可读性优先。3.3 汇总与查询按学校、按项目两个维度录入完成后实验报告通常要求输出三张表各学校总分排名、各项目获奖名单、破纪录加分明细。这三张表都靠遍历成绩记录实现不需要额外存储。/* 汇总各学校总分结果写回 schools[].total */ void sumBySchool(RecordList *list, School schools[], int schoolCnt) { for (int i 0; i schoolCnt; i) schools[i].total 0; for (int i 0; i list-length; i) { Record *r list-data[i]; int idx r-schoolId - 1; schools[idx].total r-score r-bonus; } } /* 按项目查询获奖名单rank0 才输出 */ void queryByItem(RecordList *list, Item items[], int itemCnt, int itemId) { if (itemId 1 || itemId itemCnt) { printf(项目不存在\n); return; } printf(项目%s\n, items[itemId - 1].name); for (int i 0; i list-length; i) { Record *r list-data[i]; if (r-itemId itemId r-rank 0) { printf(学校%d 运动员%d 第%d名 得分%d 破纪录%d\n, r-schoolId, r-athleteNo, r-rank, r-score, r-bonus); } } }逻辑说明sumBySchool先把总分清零再累加避免多次调用时重复累加这是汇总函数最常见的翻车点。queryByItem只输出rank 0的记录参赛未获奖的不出现在获奖名单里但破纪录加分明细需要单独查因为破纪录可能发生在非获奖名次比如预赛破纪录但决赛没进前八这个边界条件在实验报告里经常被忽略。参数说明schoolCnt和itemCnt由录入阶段确定不要用MAX_SCHOOL代替否则会输出空学校。itemId从 1 开始和用户输入保持一致内部转下标时减 1这个转换只做一次不要在每个函数里重复减。4. 避坑与排查分数统计里最容易翻车的五件事4.1 现象总分比手工算的多出一截原因sumBySchool被调用了两次第二次没有清零直接在第一次结果上累加。或者录入时把同一条记录追加了两遍重复校验只查了运动员编号没查项目编号。解决汇总函数第一行必须清零重复校验用「学校项目运动员」三元组在录入函数返回前打印一次当前记录数方便对账。4.2 现象程序在输入非数字时死循环原因scanf读到字母后返回 0坏字符留在缓冲区下一次循环继续读同一个字符输出刷屏。解决检查scanf返回值不等于预期个数时用while (getchar() ! \n);清空缓冲区。更稳的做法是用fgets读整行再用sscanf解析但实验报告里用scanf加清空就够了。4.3 现象破纪录加分没有计入总分原因sumBySchool里只加了r-score忘了r-bonus。或者bonus字段在录入时被覆盖比如先赋 0 再判断是否破纪录判断分支写反了。解决汇总时明确写r-score r-bonus录入时先读bonus原始值再根据项目类型决定是否允许破纪录田赛和径赛的纪录是分开的不要用一个全局标志。4.4 现象按项目查询时输出顺序和名次不一致原因成绩记录是按录入顺序存的不是按名次排序的。查询时直接遍历输出顺序取决于录入顺序。解决查询函数里先收集符合条件的记录到临时数组按rank升序排一下再输出。如果实验报告不要求排序至少在输出时标注「以下按录入顺序」避免读者误以为乱序是 bug。4.5 现象文件保存后再读入总分对不上原因写文件时只写了成绩记录没写学校和项目信息或者读入时fscanf的格式字符串和写入时不一致字段错位。解决文件分三段写第一段学校数、第二段项目数、第三段成绩记录数每段先写数量再写内容。读入时按同样顺序解析每读一条就校验字段范围。热搜里「fscanf结构体」的坑基本都在格式字符串上建议写入和读取用同一个printf/scanf格式宏避免手写两遍。5. 从实验报告到可演示版本三个进阶技巧5.1 用命令行参数代替交互式输入实验报告通常要求交互式菜单但演示时一条条输入很慢。我一般会加一个-f参数支持从文本文件批量导入格式就是每行一条成绩记录。这样调试时用文件交报告时用菜单两不误。/* 批量导入每行 学校 项目 运动员 名次 破纪录 */ int importFromFile(RecordList *list, const char *path, School schools[], int schoolCnt, Item items[], int itemCnt) { FILE *fp fopen(path, r); if (!fp) return -1; int sid, iid, ano, rank, bonus; while (fscanf(fp, %d %d %d %d %d, sid, iid, ano, rank, bonus) 5) { Record rec {sid, iid, ano, rank, 0, bonus}; rec.score calcScore(rank, items[iid - 1].topN); if (appendRecord(list, rec) 0) { fclose(fp); return -2; } } fclose(fp); return 0; }逻辑说明fscanf返回 5 表示五个字段都读到了返回其他值就停止这样文件末尾的空行不会导致多读一条。rec用初始化列表赋值score先填 0 再算避免未初始化。参数path由命令行传入不要硬编码文件名。5.2 用断言做自检而不是靠肉眼对账实验报告的数据量不大但手工对账很烦。我会在汇总后加几个断言总分等于所有scorebonus之和、每个学校总分不小于 0、获奖记录数不超过学校数 × 项目数 × topN。断言失败时打印具体数值比一行行看输出快得多。#include assert.h void selfCheck(RecordList *list, School schools[], int schoolCnt) { int sumAll 0, sumSchool 0; for (int i 0; i list-length; i) sumAll list-data[i].score list-data[i].bonus; for (int i 0; i schoolCnt; i) sumSchool schools[i].total; assert(sumAll sumSchool); /* 两个维度必须一致 */ }参数说明assert在NDEBUG定义时会被去掉发布版本不受影响。如果实验报告要求不能直接用assert改成if加printf也一样关键是有一个自动对账的动作。5.3 把计分规则做成配置而不是写死在代码里不同学院的秩序册计分规则可能不同前五名和前八名混用很常见。我的习惯是把topN和对应分数表放在项目结构体里或者单独一个配置文件。这样换一个运动会只需要改数据不用重新编译。配置项含义常见取值topN取奖名次范围5 或 8scoreTable名次对应分数5,4,3,2,1 或 9,7,6,5,4,3,2,1bonus破纪录加分5 或 10allowTie是否允许并列名次0 或 1这张表可以直接写进实验报告的「参数设计」一节比大段文字描述清楚得多。allowTie如果为 1计分时并列名次取相同分数下一名次跳空这个逻辑用分支结构实现不要用查表硬套。最后说一个我自己的习惯每次改完计分规则先跑一遍只有三条记录的测试数据手工算一遍总分对上了再跑完整数据。这个习惯帮我省过很多次返工因为计分规则的 bug 往往在数据量大的时候才暴露但根源在规则本身。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑