当前位置:   article > 正文

学生信息管理系统(完整版)

学生信息管理系统

        对于刚开始接触数据结构,里面的很多知识都很难理解,需要自己多动手敲代码才能提升自己的能力。先简单说说这个学生管理系统,因为考虑到总是会发生改查等功能,并且考虑到资源的合理利用,采用数组或者使用顺序表都不是最佳选(开大了,会造成资源浪费,开小了无法满足正常的使用),所以我们应该使用动态内存,需要用多少我开多少,既不浪费,也能做到持续录入学生信息。

        话不多说,上源码:

link.h文件

  1. typedef int data_t;
  2. typedef struct student//定义一个结构体,分别存学号,成绩,平均分等
  3. {
  4. int num;
  5. float Chinese;
  6. float Math;
  7. float English;
  8. float Average;
  9. struct student *next;
  10. }stu,*stup;
  11. stup creat_list();//建立头节点函数
  12. stup getnode(stup H,data_t x);//返回节点函数。这个函数作用很大,功能是返回x学号的这个学生的节点
  13. stup freelist(stup H);//释放链表函数。传一个NULL给调用的节点
  14. void help();//帮助函数
  15. void clear();//清屏函数
  16. int Type(stup H);//录入学生函数
  17. int count(stup H);//计算平均分函数
  18. int sort(stup H);//排序函数
  19. int search(stup H);//查找学生函数
  20. int del(stup H);//删除学生函数
  21. int List(stup H);//打印学生信息函数
  22. int listempty();//链表为空打印函数
  23. int length(stup H);//求链表长度函数
  24. int arrange(stup H);//整理学生信息函数
  25. int modify(stup H);//修改学生信息函数

link.c文件

  1. #include <stdio.h>
  2. #include"link.h"
  3. #include <stdlib.h>
  4. void help()//帮助手册函数
  5. {
  6. printf("**********************************\n");
  7. printf("* 学生成绩管理系统——帮助菜单 *\n");
  8. printf("**********************************\n");
  9. printf("* H = 显示帮助菜单 *\n");
  10. printf("* T = 成绩录入 *\n");
  11. printf("* A = 计算学生平均分 *\n");
  12. printf("* L = 列出成绩表 *\n");
  13. printf("* P = 按平均成绩由高到低排序*\n");
  14. printf("* S = 按学号查询学生成绩 *\n");
  15. printf("* F = 整理学生表 *\n");
  16. printf("* X = 删除学生信息 *\n");
  17. printf("* M = 修改学生信息 *\n");
  18. printf("* C = 清屏 *\n");
  19. printf("* Q = 退出程序 *\n");
  20. printf("**********************************\n");
  21. printf("* <C> 2023.0809 By 小企鹅 *\n");
  22. printf("**********************************\n");
  23. printf("请输入命令=");
  24. return ;
  25. }
  26. stup creat_list()//建立头节点函数
  27. {
  28. stup H;
  29. H=(stup)malloc(sizeof(stu));//动态分配节点
  30. if(H==NULL)
  31. {
  32. printf("H malloc is error\n");
  33. return H ;
  34. }
  35. //给定初始值,因为学生学号从1开始,头节点设置全零
  36. H->num=0;
  37. H->Chinese=0;
  38. H->Math=0;
  39. H->English=0;
  40. H->Average=0;
  41. H->next=NULL;//后继设为NULL
  42. return H;
  43. }
  44. int Type(stup H)//录入学生函数
  45. {
  46. data_t n;
  47. if(H==NULL) return -1;
  48. int a;
  49. float b,c,d,e;
  50. stup q,p;
  51. q=H;
  52. printf("请输入即将录入学生的人数:");
  53. scanf("%d",&n);
  54. printf("请输入%d名学生的三門课的成绩:\n",n);
  55. printf("学号\t语文\t数学\t外语\n");
  56. for(int i=0;i<n;i++)//循环录入,按照输入的学生人数录入
  57. {
  58. p=(stup)malloc(sizeof(stu));//开辟新节点
  59. if(p==NULL)
  60. {
  61. printf("p malloc is error\n");
  62. return -1;
  63. }
  64. scanf("%d%f%f%f",&a,&b,&c,&d);
  65. //对节点赋值
  66. p->num=a;
  67. p->Chinese=b,
  68. p->Math=c;
  69. p->English=d;
  70. p->Average=0;
  71. p->next=NULL;//节点指向空
  72. while(q->next)//遍历链表,实现尾部插入
  73. {
  74. q=q->next;
  75. }
  76. q->next=p;//实现尾部插入
  77. }
  78. printf("请输入命令=");
  79. return 0;
  80. }
  81. stup getnode(stup H,data_t x)//获取对应学号的节点函数
  82. {
  83. if(H==NULL) return NULL;
  84. if(x<0)
  85. {
  86. return NULL;
  87. }
  88. if(x==0) return H; //返回H,这个对于删除第一个节点的学生会有使用,不应该返回空。
  89. stup q;
  90. q=H;
  91. int i=0;
  92. while(i<x)//遍历查找
  93. {
  94. q=q->next;
  95. if(q==NULL)
  96. {
  97. return NULL;
  98. }
  99. i++;
  100. }
  101. return q;//返回找到的这个学生的节点
  102. }
  103. int length(stup H)//求链表长度函数
  104. {
  105. if(H==NULL) return -1;
  106. stup q;
  107. q=H;
  108. int sum=0;
  109. while(q)//遍历链表,获得长度
  110. {
  111. sum++;
  112. q=q->next;
  113. }
  114. return sum;//返回长度值
  115. }
  116. int del(stup H)//删除学生信息函数
  117. {
  118. if(H==NULL) return -1;
  119. if(H->next==NULL)//如果只有一个头节点
  120. {
  121. printf("H is empty\n");
  122. printf("请输入命令=");
  123. return -1;
  124. }
  125. stup q,p,r;
  126. int x;
  127. printf("请输入你要删除的学生的学号:");
  128. scanf("%d",&x);
  129. if((q=getnode(H,x-1))==NULL)//得到的是前一个的节点
  130. {
  131. printf("删除失败,没有对应学号的学生\n");
  132. printf("请输入命令=");
  133. return -1;
  134. }
  135. if(q->next==NULL)//前驱没有后继,代表这个节点是尾部节点,没有后继,那么删除后继显然不合理
  136. {
  137. printf("删除失败,没有对应学号的学生\n");
  138. printf("请输入命令=");
  139. return -1;
  140. }
  141. p=q->next;//暂时保存这个待删除的节点,你不保存,释放的时候,会找不到这个节点的
  142. q->next=q->next->next;//重新指向后一个节点
  143. free(p);//删除之后,释放。
  144. p=NULL;//置空
  145. printf("删除成功,输入T命令查或者输入F调整学生表\n");
  146. printf("请输入命令=");
  147. return 0;
  148. }
  149. int List(stup H)//打印学生信息函数
  150. {
  151. if(H==NULL) return -1;
  152. printf("学生成绩如下:\n");
  153. printf("学号\t语文\t数学\t外语\t平均分\n");
  154. stup p;
  155. p=H->next;//指向第一个元素,头节点不需要打印。
  156. while(p)//遍历p,打印信息
  157. {
  158. printf("%d\t%.1f\t%.1f\t%.1f\t%.1f\n",p->num,p->Chinese,p->Math,p->English,p->Average);
  159. p=p->next;
  160. }
  161. printf("请输入命令=");
  162. return 0;
  163. }
  164. int listempty()//打印为空函数
  165. {
  166. printf("成绩表为空!请先输入命令T录入学生成绩\n");
  167. printf("请输入命令=");
  168. return 0;
  169. }
  170. int arrange(stup H)//整理学生信息函数
  171. {
  172. if(H==NULL) return -1;
  173. stup q=H->next;//指向第一个节点
  174. int i=1;
  175. while(q)//遍历链表
  176. {
  177. q->num=i++; //重新给学号赋值
  178. q=q->next;//指针移动
  179. }
  180. printf("整理学生表完成,你可以输入命令L查看\n");
  181. printf("请输入命令=");
  182. return 0;
  183. }
  184. int count(stup H)//计算平均分函数
  185. {
  186. if(H==NULL) return -1;
  187. stup q;
  188. q=H->next;//指向第一个节点
  189. while(q)//遍历节点
  190. {
  191. q->Average=(q->Chinese+q->Math+q->English)/3.0;//求出对应的平均分
  192. q=q->next;
  193. }
  194. printf("平均分已计算。请使用命令L查看。\n");
  195. printf("请输入命令=");
  196. return 0;
  197. }
  198. int sort(stup H)//排序函数,采用插入排序
  199. {
  200. if(H==NULL) return -1;
  201. if(H->next->next==NULL)
  202. {
  203. printf("Only one,sort is unnecessary\n");
  204. printf("请输入命令=");
  205. return -1;
  206. }
  207. stup q,p,r,t;//建立四个节点
  208. p=H->next->next;//指向第二个元素
  209. q=r=H;//指向第一个元素
  210. H->next->next=NULL;//断开链表。
  211. while(p)//当待插入链表中还有节点
  212. {
  213. q=r=H;//每次插入一个后,节点回到初始状态。重新判断插入点
  214. while(q && p->Average <= q->Average)//当待插入的节点小于该节点,那么我就往后走,因为要保证分数高的在前面,分数低的在后面。
  215. {
  216. r=q;//r表示q前驱,因为要插入q节点这个位置,所以保留前一个位置r
  217. q=q->next;
  218. }
  219. t=p->next;//先让后续的链表用t指针保持起来。
  220. p->next=r->next;//接下来两步实现插入
  221. r->next=p;
  222. p=t;//p重新指向下一个节点
  223. }
  224. printf("学生成绩已经拍好序,请输入指令L查看\n");
  225. printf("请输入命令=");
  226. return 0;
  227. }
  228. int search(stup H)//查找学生信息函数
  229. {
  230. if(H==NULL) return -1;
  231. int e;
  232. stup q;
  233. printf("请输入要查询的学生的学号:");
  234. scanf("%d",&e);
  235. if((q=getnode(H,e))==NULL||q==H)//返回这个节点的函数为空,或者为头节点,都为非法数据
  236. {
  237. printf("查找失败,没有你要查找的学生\n");
  238. printf("请输入命令=");
  239. return -1;
  240. }
  241. printf("学号\t语文\t数学\t外语\t平均分\n");
  242. printf("%d\t%.1f\t%.1f\t%.1f\t%.1f\n",q->num,q->Chinese,q->Math,q->English,q->Average);
  243. printf("请输入命令=");
  244. }
  245. void clear()//清屏函数
  246. {
  247. system("clear");
  248. printf("请输入命令=");
  249. }
  250. int modify(stup H)//修改学生信息函数
  251. {
  252. if(H==NULL) return -1;
  253. stup p;
  254. int a,n;
  255. float b,c,d;
  256. printf("请输入你要修改学生信息的学号:");
  257. scanf("%d",&n);
  258. if((p=getnode(H,n))==NULL||p==H)//返回这个节点为空或者为头节点都是非法数据
  259. {
  260. printf("没有你要修改信息的学生\n");
  261. printf("请输入命令=");
  262. return -1;
  263. }
  264. else
  265. {
  266. printf("该学生的信息如下所示:\n");//修改之前,先打印这个学生的信息。
  267. printf("学号\t语文\t数学\t外语\t平均分\n");
  268. printf("%d\t%.1f\t%.1f\t%.1f\t%.1f\n",p->num,p->Chinese,p->Math,p->English,p->Average);
  269. printf("请输入你要修改的内容\n");
  270. printf("学号\t语文\t数学\t外语\n");
  271. scanf("%d%f%f%f",&a,&b,&c,&d);
  272. //重新赋值,实现修改
  273. p->num=a;
  274. p->Chinese=b,
  275. p->Math=c;
  276. p->English=d;
  277. p->Average=0;
  278. printf("修改信息成功,输入命令L可查看\n");
  279. printf("请输入命令=");
  280. }
  281. return 0;
  282. }
  283. stup freelist(stup H)//链表释放函数,返回值为NULL
  284. {
  285. if(H==NULL) return NULL;
  286. stup p;
  287. while(H)
  288. {
  289. p=H;//中间变量
  290. free(p);//完成释放
  291. H=H->next;//节点移动
  292. }
  293. p=NULL;
  294. return NULL;//返回给我的main函数的H,因为在哪里它替我保存了这个链表。单独在这这里H=NULL,是不行的,因为这里是局部变量,外面也起不到作用。所以使用这种简单的方式。
  295. }

test.c文件

  1. #include<stdio.h>
  2. #include"link.h"
  3. #include <stdlib.h>
  4. stup H,q;
  5. //一块的测试函数,见名知意,我就不赘述了
  6. void test_Type();
  7. void test_del();
  8. void test_count();
  9. void test_List();
  10. void test_search();
  11. void test_arrange();
  12. void test_sort();
  13. void test_modify();
  14. void test_clear();
  15. void test_help();
  16. int main()
  17. {
  18. char c;
  19. help();
  20. int d;
  21. H=creat_list();//建立头节点,H指向这个节点
  22. if(H==NULL) return -1;
  23. while(1)//循环函数
  24. {
  25. scanf("%c",&c);
  26. switch(c)
  27. {
  28. case 'T' : test_Type();//录入学生信息
  29. break;
  30. case 'X' : test_del();//删除学生信息
  31. break;
  32. case 'L' : test_List();//展示学生信息
  33. break;
  34. case 'S' : test_search();//查找学生
  35. break;
  36. case 'F' : test_arrange();//调整学生信息
  37. break;
  38. case 'A' : test_count(); //计算平均分
  39. break;
  40. case 'P' : test_sort(); //排序
  41. break;
  42. case 'M' : test_modify(); //修改学生信息
  43. break;
  44. case 'C' : test_clear();//清屏
  45. break;
  46. case 'H' : test_help();//查看帮助手册
  47. break;
  48. case 'Q' : printf("press anything quit\n");//退出
  49. H=freelist(H);
  50. q=NULL;
  51. return -1;
  52. default : printf("input error!once again\n请输入命令=");//其他
  53. getchar();
  54. break;
  55. }
  56. }
  57. H=freelist(H);//调用释放函数。
  58. q=NULL;
  59. return 0;
  60. }
  61. //需要注意的是:对于每次输入都有垃圾字符会留在缓冲区,需要使用getchar()”吃掉“。
  62. //调用这些删改查函数的时候,都应该先判断链表中节点是否为空,也就是判断有没有学生信息。
  63. void test_Type()
  64. {
  65. Type(H);
  66. getchar();
  67. }
  68. void test_del()
  69. {
  70. q=H;
  71. if(q->next)
  72. {
  73. del(H);
  74. getchar();
  75. }
  76. else
  77. {
  78. listempty();
  79. getchar();
  80. }
  81. }
  82. void test_count()
  83. {
  84. q=H;
  85. if(q->next)
  86. {
  87. count(H);
  88. getchar();
  89. }
  90. else
  91. {
  92. listempty();
  93. getchar();
  94. }
  95. }
  96. void test_List()
  97. {
  98. q=H;
  99. if(q->next)
  100. {
  101. List(H);
  102. getchar();
  103. }
  104. else
  105. {
  106. listempty();
  107. getchar();
  108. }
  109. }
  110. void test_search()
  111. {
  112. q=H;
  113. if(q->next)
  114. {
  115. search(H);
  116. getchar();
  117. }
  118. else
  119. {
  120. listempty();
  121. getchar();
  122. }
  123. }
  124. void test_arrange()
  125. {
  126. q=H;
  127. if(q->next)
  128. {
  129. arrange(H);
  130. getchar();
  131. }
  132. else
  133. {
  134. listempty();
  135. getchar();
  136. }
  137. }
  138. void test_sort()
  139. {
  140. q=H;
  141. if(q->next)
  142. {
  143. sort(H);
  144. getchar();
  145. }
  146. else
  147. {
  148. listempty();
  149. getchar();
  150. }
  151. }
  152. void test_modify()
  153. {
  154. q=H;
  155. if(q->next)
  156. {
  157. modify(H);
  158. getchar();
  159. }
  160. else
  161. {
  162. listempty();
  163. getchar();
  164. }
  165. }
  166. void test_clear()
  167. {
  168. clear();
  169. getchar();
  170. }
  171. void test_help()
  172. {
  173. help();
  174. getchar();
  175. }

运行效果截图:

        截图过多,我就不一一展示了。对于本个项目,基本的增删改查都实现了,使用简单的链表就可以实现这一功能。给大家说一下思路:

  1. 首先,我们需要一个头节点,来作为整个链表的开头,以便我能通过这个节点指针找到我的后续的数据(通过malloc函数去动态分配内存)。
  2. 建立好头节点之后,我可以先实现简单的录入功能,使用两个指针,一个用来遍历链表,一个用来得到我录入的信息,将我所输入的数据通过链表的方式进行”尾插“录入。(实现录入功能能)
  3. 先说getnode()函数,这个函数的作用是返回这个节点的地址,因为对于我实现删除或者查找甚至随机插入等的时候,我都需要先知道对应数据的前驱,也就是指向这个数据的指针,特别是对于删除功能而言,我们是应该先找到前一个结点,然后使用next指针,重新指向就可以实现这个功能。对于删除时输入的学号,也应该判断一下数据是否合法,非法打印错误信息。注:当你删除一个节点的时候,你应该使用free函数,将你删除的节点释放掉。
  4. 然后查找功能,你可以通过遍历链表的方式来获取,输入对应学号,当学号等于我的指针所指向的元素内容时,就可以将其打印(或者你可以使用getnode()函数,对于函数内部,我们可以经量少调用函数,虽然调用起来方便,但在一定程度上会影响效率)。遍历完还没找到,说明数据非法,那么当然会打印错误提示信息。
  5. 对于修改功能来说,首先也是对于数据非法的处理,非法就打印错误信息等。修改功能的实现,也很简单,通过遍历链表,找到了这个学号的节点,重新录入信息即可。
  6. 对于学生成绩排序这一功能,使用的是插入排序,也就是先断开节点。当我待插入的值,在合适位置的时候,通过改变指针指向完成插入。

        以上是这个项目的简单流程,一些整理学生表的功能我就不赘述了,这个功能一般使用在删除了学生之后的时候,我可以使用这个函数整理一下我的学生表。

        最后说说实现这个项目的一些经历和过程吧:

        代码实现还是比较简单的,学习数据结构的话,应该多动手画画图,便于理解,在这个过程中,你会对指针的使用更上一步,当然也有很多地方有很多坑,比如指针总是指向一些未知区域,这是在指针移动的时候,你没有做好结束条件的判断,倘若在这之后你还在修改里面的内容的话,那么后果是无法想象的。总之也就是说:

  • 在使用这个节点之前,先看是否定义好。
  • 其次看指针是否指向正确。
  • 节点是否是NULL。
  • 对于循环处理的时候,应该先检查结束条件,就像写for()循环一样,应该写好初值,写好结束条件,写好改变值。不然你在后期使用这个节点的时候,也会出现段错误等错误。
  • 在传入头节点的时候,都应该先判断这个节点是否开辟好了(每个都应该判断)。
  • 因为这是动态分配的内存,我们应该malloc和free成对出现,一定要养成良好的习惯。
  • 注意一下对于调用freelist()函数的时候,里面的H是个局部变量,你free之后置NULL其实也没什么作用,因为到了main里面,这个指针还是有指向,你后期还是可以操作到。这里应该使用最简单的传一个NULL给H,保证H置空。
  • 最后,对于调试功能的话,可以使用gdb设置断点一步一个调试,实现一个功能测试一个功能,别以测正确数据为目的,我们更多应该要测试的是那些非法数据。这样才能更加提升我们的能力。

        以上是小编初学链表的一些心得与体会。希望对你有所帮助。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/449923
推荐阅读
相关标签
  

闽ICP备14008679号