大二下学期开了数据结构课,老师布置了一个课程设计,要求用C语言写一个成绩管理系统。刚开始觉得挺简单的,无非就是增删改查,真正动手写起来才发现,难度全藏在那些不起眼的小细节里。
我先分析了一下需求。这个系统要能录入学生信息和三门课的成绩,能按学号或姓名查找,能修改删除记录,还要能按平均分排序,统计各科平均分和及格率,最后把数据存到文件里,下次启动时能读出来。数据的存储结构用单向链表,学生信息包含学号、姓名、C语言成绩、数据结构成绩、英语成绩这几个字段。
设计结构体的时候,我遇到了第一个坑。头尾指针是全局变量还是放在main函数里传参?全局变量写起来省事,但容易出隐患,最后决定用局部变量,每次函数调用都手动传指针。中间涉及大量的指针传递和修改操作,经常写错,好在用断点调试慢慢排掉了。
录入环节我设计成一次可以连续输入多个学生,输完后按0返回主菜单。写的时候要注意输入缓冲区和循环控制的关系,不然很容易跳出死循环。我还加了一个限制条件,超过五十个学生就提示不能再录了。排序我用的是冒泡排序,虽然效率一般,但是写起来逻辑清晰,不会写错。排序键设置为平均分,比较的时候用if语句交换节点里的数据,而不是交换节点本身,这样省去不少指针操作的烦恼。
查找功能我做了两种方式,支持按学号精确查找,也支持按姓名模糊查找。打印学生信息的时候,我会顺便显示平均分和总分,排一个很整齐的表格。修改的时候先查到这个学生的节点,然后逐个字段重新输入,输入完校验一下合法性。

统计模块是最让我头疼的,因为要算各种数据。后来我写了个通用的统计函数,传入科目编号,返回平均分和及格率,循环调用三次就能算出三科的成绩指标。这样代码重复率低,也方便以后加科目时扩展。
文件存储我采用的格式是文本文件,每行一个学生,字段间用空格分隔。写文件用fprintf,读文件用fscanf,遇到文件不存在的情况就默认当成空链表处理。最后记得fclose,这个细节很容易漏。
系统的菜单我用do-while循环加switch-case实现,退出条件设为输入的选项为6。每执行完一个功能后稍等几秒再清屏返回主菜单,这样不会感觉操作太仓促。
调试过程其实花了好几个小时。最常见的问题是段错误,基本都是空指针或野指针引起的。比如删除节点时没有保存后继节点的地址就直接释放了当前节点,导致下一步访问出错。还有就是边界条件判断,比如空链表排序,只输入一个学生时排序,这些情况如果没处理就会出问题。
用户交互体验方面我给每步操作都加了明确提示,输入数据时也做了范围校验,比如学号必须是数字,性别只能输M或F。成绩范围限制在0到100之间,超了就要求重新输入。这样能减少不少误操作。
写完差不多四百多行代码。运行起来效果不错,录入20个测试学生的数据后,排序、统计、修改、删除这几个功能都能正常工作。平均分排序列出来每门课的及格率也算得准。最后把数据保存到文件,退出程序再启动,数据能完整读回来。
这次课设让我最大的收获是理解了链表的逻辑,以前光看书总觉得抽象,真正写了几百行代码用完之后,链表的结构和操作就变得非常清晰了。同时也体会到结构化编程的思想,把一个大系统拆成一个个功能函数去实现,每个函数各司其职,组合起来就能完成复杂的任务。
现在想想,如果重新写一遍,我会在链表操作的地方封装得更干净,比如单独的插入函数和删除函数,这样代码复用性更高。也许还可以加上按课程成绩分布统计的功能,比如多少分以上的有多少人,做成柱状图显示,但那可能就要用到图形库了,这次时间有限就暂时不做了。