简介面向计算机专业C语言课程设计学习者这份教务信息管理系统实验报告对应华中科技大学《C语言程序设计》课程设计题目适合需要完成管理系统类课设、理解链表与模块化编程的读者参考。压缩包仅含1个doc文件约361KB正文围绕需求说明、数据结构、流程图与源代码展开内容以班级、学生、课程、成绩四重十字交叉链表组织数据并介绍图形化中文菜单、文件保存与打开、信息增删改查、排序、模拟打印及班级与课程均分统计等功能。已有1121人学习下载。读者可据此梳理课设报告结构对照链表节点设计、模块拆分与功能实现思路理解不及格重修记录、按类分文件保存等细节用于选题、撰写报告或复查程序逻辑具有较强的课程设计参考价值。1. 一份 2006 年的 C 语言课程设计为什么还值得逐行拆翻课程设计资源的时候十份里有八份是图书管理系统能碰到一份把教务信息管理系统完整做下来的 C 语言课程设计已经算运气。这份东西骨架不复杂十字交叉链表撑起班级、学生、课程、成绩四层数据Turbo C 图形模式下自绘中文菜单九个.c文件靠.prj工程拼成一个 exe落盘是四个纯文本文件再加一份可直接打印的报表。它解决的是“把辅导员手里那张年级成绩表搬到机器上”这件事——录入、增删改查、按学号或按加权排序、班级均分与不及格名单统计一条线走通。适合两类人正卡在课程设计上、需要一个能跑通完整样例的在校生以及想拿老代码练链表、定长序列化和 DOS 图形编程的工程师。年头是久了点但里面那些坑一个都不过时。2. 十字交叉链表四层结构体的字段怎么定、链怎么挂这套数据结构的野心在名字里就写着——“十字交叉”本质是四重链班级链向下挂学生链学生链向下挂课程链课程链向下挂成绩链。它不像关系型数据库那样靠外键连接而是靠指针硬挂同时又在每个子节点里冗余一份父节点的编号用来在只拿到一个成绩节点时反向定位“这是哪个班、哪个学生、哪门课”。这两套定位机制叠在一起是整份代码里最值得先吃透的部分。2.1 字段全部用 char 数组定长设计的三笔账四个结构体里没有一个int、没有一个float学号、姓名、成绩、学分、加权分统统是char数组。第一次看会觉得别扭算一遍账就明白了文件要能直接用记事本打开、还不能有乱码浮点数用%f写进去是85.500000这种长度不定的串读回来对不齐列而定长字符串写出去是等宽的一行一条记录肉眼就能改。代价是每个字段的长度必须精确等于“可见字符数 1”多一个字少一个字都会在strcpy那里翻车。/* 成绩链一条记录 某学生某课程的一次考试 */ typedef struct scores { char fall3[4]; /* 从属关系fall3[0]班级 fall3[1]学生 fall3[2]课程 */ char number[2]; /* 考试次数只放得下 1 位数字 */ char exam_score[4]; /* 考试成绩最大 3 位 */ char usual_score[4]; /* 平时成绩 */ char coll_score[4]; /* 综合成绩 */ char date[11]; /* 考试时间2005-2006 这类学期串 */ struct scores *next4; } SCORE; /* 课程链fall2[0]班级 fall2[1]学生 */ typedef struct subjects { char fall2[3]; char course_no[11]; char course_name[17]; char credit[4]; /* 学分如 2.5、5.5 */ struct subjects *next3; SCORE *chead; /* 成绩链头指针 */ } SUBJECT; /* 学生链fall1[0]班级 */ typedef struct students { char fall1[2]; char student_no[13]; char name[17]; char sex[2]; /* 0 男 1 女 */ char age[3]; char phone[12]; char com_phone[12]; char jia_quan[7]; /* 加权分如 78.35 */ char all_credit[4]; /* 已得总学分 */ struct students *next2; SUBJECT *suhead; } STUDENT; /* 班级链 */ typedef struct classes { char class_no[5]; struct classes *next1; STUDENT *sthead; } CLASS;逻辑说明每个结构体末尾的指针是“向下”的链next1/next2/next3/next4分别串起班级、学生、课程、成绩sthead/suhead/chead是“向下”的头指针。父子关系靠这两组字段区分——nextN找兄弟*head找孩子别用反。参数说明student_no[13]正好放 12 位学号加\0这是按当年学号规则卡的course_name[17]放 16 个字符拼音课程名如weijifen够用但换成长中文课名UTF-8 下一个汉字 3 字节必崩number[2]只能存0~9重修到第 10 次就写溢出了这是个真实的暗雷后面第 5 章会专门说jia_quan[7]放100.00刚好再多一位小数就断。实测经验定长字段一律按“最大可见长度 1”给宁可留空也别抠char credit[4]存5.5没问题存12.5就只剩 3 个字符位置strcpy会连结束符一起写穿到next3上表现就是遍历到这门课时指针变成乱码地址程序直接黑屏退出。2.2 哨兵 “00” 与建链顺序一次只建一层录入流程不是一次性把四层填完而是靠00这个哨兵值逐层回退建成绩时输入00回到课程层建课程时输入00回到学生层建学生时输入00回到班级层建班级时输入00结束整个录入。这套设计的好处是不用预先知道要录几个班几门课坏处是每一层都要处理“上一层的半成品状态”——比如一个班里一个学生都没录就00退出这个空班级节点要不要保留。/* 在指定班级下建学生链输入 00 结束返回挂上的学生个数 */ int build_students(CLASS *cls) { STUDENT *head NULL, *tail NULL, *p; char buf[32]; int cnt 0; while (1) { printf(请输入学号(输入00结束): ); scanf(%s, buf); if (strcmp(buf, 00) 0) break; /* 哨兵回到班级层 */ if (strlen(buf) 12) { /* 长度护栏防止写穿 */ printf(学号超长最多12位\n); continue; } p (STUDENT *)malloc(sizeof(STUDENT)); if (p NULL) { printf(内存不足\n); break; } /* 小模式堆很紧 */ memset(p, 0, sizeof(STUDENT)); /* 定长字段必须清零 */ strcpy(p-student_no, buf); strcpy(p-fall1, cls-class_no); /* 冗余父节点编号供反查 */ p-next2 NULL; p-suhead NULL; printf(姓名: ); scanf(%s, p-name); printf(性别(0男1女): ); scanf(%s, p-sex); printf(年龄: ); scanf(%s, p-age); printf(寝室电话: ); scanf(%s, p-phone); printf(手机: ); scanf(%s, p-com_phone); strcpy(p-jia_quan, 0); strcpy(p-all_credit, 0); if (head NULL) head p; /* 头插还是尾插这里选尾插 */ else tail-next2 p; /* 保持录入顺序方便回看 */ tail p; cnt; } cls-sthead head; /* 整条链一次挂上去 */ return cnt; }逻辑说明先把整条学生链在局部变量里建完最后一步才cls-sthead head。这一点很关键——如果边建边改cls-sthead中途用户按00或malloc失败班级里就会挂着一条半截链后续保存和统计都会读到脏数据。参数说明buf[32]是输入缓冲比最长字段student_no[13]宽先接住再校验长度避免scanf(%s)直接把超长串写进定长字段memset清零不能省malloc拿到的内存是脏的末尾没有\0后面strcmp会把整块堆内存当字符串读到崩溃fall1冗余存班级号是为了后面“只给成绩记录、反查是哪个班”时不回头遍历整棵树。一个必须提前说清的边界这套结构默认所有学生的课程完全相同所以课程链是挂在学生下面的同一门课在每个学生那儿都有一份独立副本。真要支持选修课课程链得提到班级层学生和课程之间再加一层选课关系节点那是另一个数据结构了课设阶段不建议动。2.3 挂链顺序与级联释放删一个班要按逆序拆建链是自上而下先班级再学生再课程再成绩释放必须自下而上先成绩再课程再学生再班级。反过来做先free(cls)再去取cls-sthead拿学生链读的是已释放内存在小模式里通常不立刻报错跑几次才崩这是最难查的一类问题。/* 删除整个班级成绩 - 课程 - 学生 - 班级一级都不能跳 */ void free_class(CLASS *cls) { STUDENT *s cls-sthead, *sn; SUBJECT *j, *jn; SCORE *k, *kn; while (s ! NULL) { j s-suhead; while (j ! NULL) { k j-chead; while (k ! NULL) { /* 最底层先清 */ kn k-next4; free(k); k kn; } jn j-next3; free(j); j jn; } sn s-next2; free(s); s sn; } free(cls); } /* 从链表里摘掉一个班级节点返回是否成功 */ int del_class(CLASS **head, const char *class_no) { CLASS *p *head, *prev NULL; while (p ! NULL strcmp(p-class_no, class_no) ! 0) { prev p; p p-next1; } if (p NULL) return 0; /* 没找到 */ if (prev NULL) *head p-next1; /* 删的是头节点必须回写 head */ else prev-next1 p-next1; free_class(p); return 1; }逻辑说明free_class用的是三层嵌套的“先存 next 再 free”套路每一个free之前都把后继指针保存到临时变量因为节点一旦释放里面的next字段就不保证还是原值。del_class用二级指针CLASS **head而不是返回新头指针是为了统一处理“删除头节点”这个分支——用一级指针在这里是最常见的漏写 bug。参数说明class_no是 4 位班级号删除前一般要先strcmp匹配并打印一遍待删班级下的学生名单让用户确认课设里省略这一步也能跑但误删一个班就是整学期数据没了建议加。另外如果程序里还有别的地方缓存了指向这些节点的指针比如查找结果里保存了STUDENT *last_found删完必须先置 NULL否则就是野指针。3. 让九个 .c 文件编译成一个 exe工程配置与图形模式起步原始报告里有一句很实在的吐槽写到两千行 TC2.0 跑不动换 Win-TC 到四千行又跑不动换 TC3.0 到五千行还是跑不动最后靠模块化拆文件才解决。这不是玄学是 DOS 下 small 内存模型的 64KB 数据段上限单个.c文件把所有全局数组和字符串常量塞进去编译器先崩给你看。所以这份工程的结构不是“为了好看才拆”是被逼出来的理解这一点再去看文件划分才有意义。3.1 九个源文件的职责切分文件名职责对外依赖main2.c主控、登录界面、主菜单与按键分发所有模块files.c文件菜单新建、打开、保存、退出in_out.cin_out.c四个 txt 的序列化与反序列化结构体定义edit.c插入、删除、修改、排序查找函数search.c班级、学生、课程查找支持前缀匹配无tongji.c班级均分、学生均分与排名、不及格名单无da_yin.c模拟打印屏显预览 报表文件输出无help.c帮助界面无qita.c通用工具清屏、按键读取、字符串裁剪无这张表是按“谁能被谁调用”切的不是按功能重要性切的。判断切得对不对有个土办法把任意一个文件单独拿掉其余文件应该还能编译通过链接会失败但编译不该报错。切错的典型症状是两个模块互相#include对方的头文件TC 的预处理器没有 include guard 保护时会无限递归。头文件里只放结构体定义、extern变量声明和函数原型绝不放变量定义——多个.c同时#include一个定义了全局变量的头文件链接期直接报Duplicate symbol。3.2 用 .prj 工程文件把模块串起来在 TC 的 IDE 里做法很固定把九个.c全部打开Project - Open Project输入jiaowu.prj再Project - Add Item逐个添加最后CtrlF9编译链接产出jiaowu.exe。.prj就是个纯文本清单一行一个文件名手工写也行。习惯用命令行的可以直接走tccecho off REM build.bat —— 在 TC3.0 的 BIN 目录下执行产出 jiaowu.exe REM -ml large 模型函数指针和数据指针都是 far突破 64KB 数据段 REM -N 每个函数做栈溢出检查调试阶段必开 REM -w 打开全部警告定长字符串越界主要靠它提醒 tcc -ml -N -w -L..\LIB -I..\INCLUDE ^ main2.c tongji.c da_yin.c in_out.c ^ files.c edit.c help.c search.c qita.c if errorlevel 1 goto fail echo 编译成功生成 main2.exe goto end :fail echo 编译失败检查上面第一条报错 :end pause逻辑说明^是 bat 的续行符写在一行里也可以-L和-I指向 TC 自带的库和头文件目录路径随安装位置改if errorlevel 1只判断“是否非零”够用因为真正原因已经在上面输出了。参数说明-ml换成-mssmall会立刻暴露内存问题正好用来验证是不是结构体或全局数组吃掉了数据段-N会拖慢运行速度但能定位栈溢出栈溢出在链表递归遍历里非常常见-w的警告不要习惯性忽略strcpy长度不匹配这类问题编译器早就提示过只是当年很多人不看。提示.prj里列的文件顺序会影响链接顺序把main2.c放第一个含main()的模块优先能减少“未定义符号”的排查时间。3.3 BGI 图形初始化与中文菜单的两种画法整个程序的界面跑在图形模式下菜单、边框、动态登录页都是画出来的。初始化就三行但三个参数每个都能卡住人#include graphics.h #include conio.h int g_driver DETECT, g_mode, g_err; void init_screen(void) { /* 第三个参数是 BGI 驱动路径exe 与 EGAVGA.BGI 同目录时传 */ initgraph(g_driver, g_mode, C:\\TC\\BGI); g_err graphresult(); if (g_err ! grOk) { /* 驱动没找到必须拦否则后面全是黑屏 */ printf(图形初始化失败: %s\n, grapherrormsg(g_err)); exit(1); } setbkcolor(BLUE); cleardevice(); } void draw_menu(int cur) { int i; char *items[5] {文件, 编辑, 打印, 统计, 查找}; settextstyle(DEFAULT_FONT, HORIZ_DIR, 1); for (i 0; i 5; i) { if (i cur) { /* 当前项反色这是唯一的选中反馈 */ setfillstyle(SOLID_FILL, WHITE); bar(40 i * 90, 20, 120 i * 90, 50); setcolor(BLUE); } else { setcolor(WHITE); } outtextxy(48 i * 90, 28, items[i]); } }逻辑说明initgraph的第三个参数传的是驱动文件所在目录传空串表示当前目录graphresult()必须在initgraph之后立刻调晚一步错误码就被后面的绘图调用覆盖了。draw_menu每次重画整个菜单条而不是只改高亮项在 386 机器上会闪但在现代机器上跑 DOSBox 完全看不出来课设阶段这样写最省事。参数说明DETECT让 BGI 自己探测显卡类型g_mode由它回填settextstyle的第一个参数DEFAULT_FONT是 8x8 点阵英文字体它画不了中文。中文菜单在 TC 下只有两条路一是整个程序跑在 DOS 中文环境里用printf或cputs输出靠中文系统接管字符显示二是自己准备 16x16 点阵字库把每个汉字的字模用putimage贴到屏幕上。前一条路省事但依赖运行环境后一条路可控但工作量大这份课设走的是前一条所以在没有中文环境的机器上打开就是一堆乱码方块——这不是程序 bug是环境缺失第 5 章会展开。4. 四个 txt 的存取一个文件名派生出四条记录流保存和打开这一对函数是整份代码里调用最频繁、也最容易在答辩现场翻车的部分。它的约定很朴素用户只输入一个不带后缀的路径程序自己拼出四个文件名分别存班级、学生、课程、成绩。e:\abc进去出来的是abc1.txt到abc4.txt。4.1 命名派生规则与“打开前先判脏”/* 由用户输入的基础名派生四个文件名后缀 1~4 对应四层链表 */ void make_name(const char *base, int idx, char *out) { sprintf(out, %s%d.txt, base, idx); /* base 里不要带 .txt */ } /* 保存base 形如 e:\\abc */ int save_all(const char *base, CLASS *head) { char fn[64]; FILE *fp; CLASS *c; STUDENT *s; SUBJECT *j; SCORE *k; make_name(base, 1, fn); if ((fp fopen(fn, w)) NULL) return 0; for (c head; c ! NULL; c c-next1) fprintf(fp, %s\n, c-class_no); /* 班级文件只有班号一行一条 */ fclose(fp); make_name(base, 2, fn); if ((fp fopen(fn, w)) NULL) return 0; for (c head; c ! NULL; c c-next1) for (s c-sthead; s ! NULL; s s-next2) fprintf(fp, %s %s %s %s %s %s %s %s %s\n, /* 9 个定长字段 */ s-fall1, s-student_no, s-name, s-sex, s-age, s-phone, s-com_phone, s-jia_quan, s-all_credit); fclose(fp); return 1; /* 课程、成绩两个文件同理略 */ }逻辑说明写文件用fprintf而不是fwrite就是为了让人能在记事本里直接看到内容、直接改。每个字段之间用空格分隔因为定长字段内部不会有空格姓名是拼音、电话是数字这个前提一旦被破坏比如姓名里带空格整行解析就错位。参数说明make_name里base不能带.txt否则出来是abc.txt1.txtfn[64]要能放下完整路径DOS 下 8.3 文件名短现代路径动辄几十字节给 64 是保险每个fopen都判 NULL磁盘满或路径不存在时要能返回失败让上层提示而不是继续往空指针里写。“打开前先判脏”是另一个必须有的动作任何一次新建、打开、退出之前都要检查当前链表是否被修改过且未保存。实现上不需要真的比对内容用一个全局标志dirty插入、删除、修改、排序成功后置 1保存成功后清 0就够用了。4.2 定长字符串读回的三个边界写出去容易读回来才是坑。fscanf(fp, %s, buf)遇到空格就停定长字段如果含空格会被切成两半而%s本身不检查目标缓冲区大小超长输入直接溢出。稳妥的写法是先读整行再按位切/* 读一行按空格切出定长字段返回字段个数-1 表示文件结束 */ int read_fields(FILE *fp, char fields[][20], int maxf) { char line[256]; int n 0; char *p; if (fgets(line, sizeof(line), fp) NULL) return -1; p strtok(line, \t\r\n); /* \r 不能漏Windows 换行会带进来 */ while (p ! NULL n maxf) { strncpy(fields[n], p, 19); /* 目标字段上限 20留 1 位结束符 */ fields[n][19] \0; /* strncpy 不保证结尾手动补 */ n; p strtok(NULL, \t\r\n); } return n; }逻辑说明fgets有长度保护strtok负责切分strncpy加手动补\0保证不会写穿。\r出现在分隔符集合里很关键不处理的话最后一个字段末尾会挂一个回车符strcmp比对永远不相等症状是“文件明明有数据打开却说没找到”。参数说明line[256]要能容纳最长一行的全部字段fields[][20]的第二维 20 是按最长字段学号 13 位留的余量maxf传 9 就是学生记录的字段数传多了会把下一行内容也吃进来。另一个边界是空链表。新建之后一个班都没录就点保存四个文件都会生成但里面是空的再次打开时读到 0 条记录程序应该正常进入空状态而不是报错。这个分支最容易被漏掉因为它不会崩只是“看起来没反应”。4.3 模拟打印屏显预览与报表文件双通道没有打印机的年代做法是把要打印的内容先输出到屏幕让用户预览同时往一个固定路径如e:\student.txt写一份带表头的报表。屏幕那一路用图形模式下的outtextxy逐行贴配上下左右方向键滚动视口文件那一路用fprintf拼版列宽靠%-12s补空格对齐。/* 打印一份带表头的成绩报表同时可回显到屏幕 */ void print_report(const char *path, CLASS *head) { FILE *fp fopen(path, w); CLASS *c; STUDENT *s; SUBJECT *j; SCORE *k; if (fp NULL) { printf(报表文件打不开\n); return; } fprintf(fp, %-6s%-14s%-10s%-10s%-8s\n, 班级, 学号, 姓名, 课程, 综合); for (c head; c ! NULL; c c-next1) for (s c-sthead; s ! NULL; s s-next2) for (j s-suhead; j ! NULL; j j-next3) for (k j-chead; k ! NULL; k k-next4) fprintf(fp, %-6s%-14s%-10s%-10s%-8s\n, c-class_no, s-student_no, s-name, j-course_name, k-coll_score); fclose(fp); }逻辑说明四层嵌套 for 循环把整棵树摊平成一张二维表这是“树转表”的标准写法%-6s左对齐补空格保证列对齐汉字列宽会歪一个汉字占两列宽但拼音数据没问题。参数说明path在课设里写死成e:\student.txt换个机器就是打开失败所以合格的做法是让用户输入或落到当前目录student.txtcoll_score是综合成绩如果要输出平时和考试分列再多加两个%-8s即可注意列宽总和不要超过一行。5. 排查手册这份课设最容易翻车的五处调试这段代码有个特点——它不会给你漂亮的报错大部分时间是直接黑屏、乱码或者死循环。下面五条是按“我当年调了几天才找到”的痛感排序的每条都按现象、原因、解决写清楚。5.1 现象程序莫名退出指针打印出来是乱码原因定长字段写穿。char credit[4]存12.5需要 5 字节strcpy会多写一个字节正好覆盖结构体里紧随其后的next3的低位字节链表在那一处就断了。小模式下这种越界不一定立刻崩遍历到该节点才炸所以看起来像是“链表自己坏了”。解决凡是strcpy到结构体字段的地方前面都加长度检查或者统一换成strncpy(dst, src, sizeof(dst)-1)再手动补\0。更省事的办法是把所有字段长度按“最大可能长度 2”给多留一位缓冲代价是文件里列宽略微变宽。5.2 现象超过两千行就编译不过报内存不足原因DOS small 模型数据段只有 64KB全局数组、字符串常量、静态变量全挤在里面。当年 TC2.0 到两千行、Win-TC 到四千行、TC3.0 到五千行依次顶不住就是这个上限在起作用跟编译器新不新没关系。解决三个动作一起做。第一拆模块把功能分到九个.c文件里单文件体积降下来第二编译加-ml或-mh用大模式/巨模式指针变 far第三把大数组从全局改成malloc从堆上要堆能用到 640KB 常规内存。原始报告里“去掉几个耗内存的语句就好了”说的就是第三件事。5.3 现象排序完成后输出顺序对了但再插一条数据就全乱原因排序时交换的是节点内的数据域而某些字段比如suhead课程链头指针忘了跟着一起交换或者用指针重排法但只改了next没改前驱的next头指针也没回写。解决课设阶段最稳的做法是只交换数据域不动指针——把整个STUDENT结构体除next2、suhead外的字段用一个临时结构体整体对调指针保持原位。这样链结构完全不变排序只影响显示顺序插入删除都不受影响。代价是数据搬运量大几千条记录在 DOS 下慢一点但不会错。5.4 现象打开文件后数据少了一半或者姓名变成上一行的学号原因写入和读取的字段顺序、字段个数不一致或者读的时候用scanf(%s)遇到字段内空格就截断后面的字段依次前移整行错位。窗口换行符\r\n里的\r没被当分隔符处理也会让最后一个字段带上不可见字符。解决写和读用同一份格式串集中在一个函数里读用fgetsstrtok分隔符集合写全 \t\r\n每次打开文件后先打印前三条记录核对格式对不上立刻能看出来。5.5 现象图形界面退出后花屏或在没有中文环境的机器上全是方块原因一是initgraph之后没有closegraph()显卡还停在图形模式回 DOS 时字符模式没恢复二是中文靠 DOS 中文环境提供字模环境不在就是方块。解决所有退出路径正常退出、Esc返回、错误退出统一走一个cleanup()里面closegraph()后再textmode(LASTMODE)双保险中文显示要么在启动时检测中文环境并在缺失时降级成英文菜单要么老老实实自带点阵字库把汉字用putimage画出来这条路一劳永逸但要多写两百行。注意调试这类程序时别在 IDE 里一路F8单步链表操作一旦跨函数就会跟丢上下文。正确姿势是在插入、删除、保存三个动作前后各打一次“遍历并打印整条链”的快照前后对比一眼就能定位是哪一步把链弄断的。6. 进阶把加权分和不及格统计做成可复核的口径统计模块看着最简单其实是最容易在答辩时被问住的地方。加权分到底怎么算、不及格名单按综合成绩还是按考试成绩判、重修记录算不算进均分这三件事只要口径没定死代码跑出来的数字就没法复核。我一般会把口径写死在注释里再让代码严格照做加权分 Σ(综合成绩 × 学分) / Σ学分只统计有成绩记录的课程不及格判定看综合成绩是否低于 60重修记录单独成链不参与加权计算只在不合格名单里标注次数。/* 计算单个学生的加权分结果写入 stu-jia_quan 和 stu-all_credit */ void calc_weighted(STUDENT *stu) { SUBJECT *j; SCORE *k; /* 取该课程最近一次考试 */ double sum_score 0.0, sum_credit 0.0; for (j stu-suhead; j ! NULL; j j-next3) { double credit atof(j-credit); double score 0.0; int found 0; for (k j-chead; k ! NULL; k k-next4) { score atof(k-coll_score); /* 后面覆盖前面 取最后一次 */ found 1; } if (!found) continue; /* 没成绩的课不计入分母 */ sum_score score * credit; sum_credit credit; if (score 60.0) { /* 及格才计学分这里按已得总学分的口径 */ sprintf(stu-all_credit, %.1f, sum_credit); } } if (sum_credit 0.0) sprintf(stu-jia_quan, %.2f, sum_score / sum_credit); else strcpy(stu-jia_quan, 0.00); /* 除零保护必须显式写 */ }逻辑说明外层遍历课程内层遍历该课程的成绩链score每轮被覆盖循环结束自然留下最后一条等价于“取最近一次考试”。sum_credit同时被当作分母和已得学分的累加器写成这样是为了少开一个变量代价是可读性下降实际项目里建议拆成两个。参数说明atof在stdlib.h里别忘#includesprintf(%.2f)写进jia_quan[7]100.00正好 6 位加结束符 7 位再多一位小数就溢出这个长度是卡死的sum_credit 0.0的判空不能省一个学生一门课都没录时直接除零DOS 下整数除零会挂浮点除零给出inf再sprintf进去就是一串乱码。还有一个容易被忽略的点统计学生均分之后要做“按加权从高到低排名输出”排名本身不需要真的排序链表只要把每个学生的jia_quan取出来做一次降序排序、再按名次遍历输出就行。但原始实现里是直接对链表排序的这就带来一个副作用——排序会改变链表顺序进而影响“加权相同时按链表顺序排”这条规则的可复现性。我一般会在排序前把链表序号记下来作为第二关键字这样同样的数据无论排几次结果都一样答辩时能对得上。从那以后我每次写统计类代码都强制先把口径写在函数头注释里、再用这组口径去生成一份对照表跑完逐行比对数字对不上就不往下走。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
表达式求值入门:栈结构、运算符优先级与取模处理实战 1. 这道"表达式求值"到底考什么:先把题目拆开看第一次在题库里刷到 P1981 这道题,很多人的反应是"就这?"——输入一行只含加号、乘号和数字的字符串,求值,最后对 10000 取模。看上去像小学三年级的… · 2026/11/9 20:00:17
从调API到啃源码:大模型底层架构深度拆解 1. 从调API到啃源码:为什么非要看懂大模型底层架构先聊个现象。这两年大模型火得不行,身边很多人都在用,调个OpenAI的接口、跑个开源模型推理、拿LangChain拼个Agent,看起来都挺顺手。但真到了要自己做点东西的时候——比如想优化… · 2026/11/9 20:00:17
Maven解压配置三道关:路径、环境变量与本地仓库 1. 为什么解压Maven不是“点开压缩包拖进文件夹”就完事? 很多人第一次接触Maven,看到官网下载页那个apache-maven-3.x.x-bin.zip文件,下意识就双击打开、全选复制、粘贴到D:\tools\maven——然后在IDE里狂点“刷新依赖”,发现报… · 2026/11/8 5:59:01
太原市特种工报名时间真实经历:没证就找不到好工作 太原市特种工报名时间真实经历:没证就找不到好工作 我认识的一个朋友,大学毕业后在太原找了好几个月的工作,简历投了几十份,面试也去了十几家,但每次都被问到一句话:“你有特种作业证吗?”最后他咬牙报了太原市的低压电工证,两个月后拿到证,直接入职… · 2026/11/9 20:00:14
广安保电工证在哪里考试多少钱?新手避坑指南来了 广安保电工证在哪里考试多少钱?新手避坑指南来了 你是不是也遇到过这种情况:听说要考电工证,上网一查,信息五花八门,有的说在广安,有的说在成都,还有的说在网上就能搞定,结果一问多少钱,价格差得离谱,搞得你根本不知道该信谁?其实,这事儿真不难,… · 2026/11/9 20:00:14
工地太忙根本没时间复习考试?绍兴上虞电工证怎么考与怎么报名全攻略 工地太忙根本没时间复习考试?绍兴上虞电工证怎么考与怎么报名全攻略 工地太忙根本没时间复习考试,你是不是也这样?每天从早忙到晚,连吃饭都得掐着点,更别说抽时间去学习考证了。可你知道吗?在绍兴上虞,像你这样的电工从业者,要是没有电工证,连上岗都… · 2026/11/9 20:00:14
电工作业必须带手套吗 上岗必备的防护与薪资前景全解析 电工作业必须带手套吗 上岗必备的防护与薪资前景全解析 怕考不过白交培训费,这是很多准备报考电工证的朋友最担心的事。电工作业必须带手套吗?这个问题看似简单,却关系到你能否安全上岗、顺利拿证、甚至影响你未来的薪资发展。今天就从【上岗必备】的角度… · 2026/11/9 20:00:14
临沂电工证在哪里报名多少钱 低压电工证报名门槛真不低 临沂电工证在哪里报名多少钱 低压电工证报名门槛真不低 你是不是也担心,自己学历不高,报不了临沂电工证?别急,今天我就用10年经验告诉你,怎么在不花钱找中介、不走弯路的前提下,顺利完成低压电工证的报名流程,而且还能清楚知道 多少钱… · 2026/11/9 20:00:14
电工证从哪办了 怎么报名才能避免过期尴尬 电工证从哪办了 怎么报名才能避免过期尴尬 证书过期了不知道怎么复审延期,这是很多电工从业者最头疼的问题。特别是像你这样在信阳市政公用工程一线工作的朋友,电工证不仅是上岗的“通行证”,更是保障自己和同事安全的“护身符”。如果证书过期了,不仅面… · 2026/11/9 19:58:53
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/9 2:05:04
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/9 3:11:26
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/9 5:24:17
电工证不需要考复审延期怎么办理 通过率揭秘 电工证不需要考复审延期怎么办理 通过率揭秘 不知道去哪报名怕被中介坑?电工证不需要考复审延期怎么办理?最近不少学员都在问这个问题,尤其是刚拿到证的新手,对复审流程一知半解,生怕被培训机构套路。别担心,今天咱们就来聊聊电工证复审的真相,尤其是… · 2026/11/9 0:00:54
砀山哪里考电工证的多 电工证报考别踩坑 郑州报考避坑指南 砀山哪里考电工证的多 电工证报考别踩坑 郑州报考避坑指南 工地太忙根本没时间复习考试,这几乎是所有一线施工人员的共同心声。尤其是像电工这样的特种作业,操作证不仅关系到能不能上工,还直接关系到工资高低和职业发展。可偏偏,很多人连怎么报名、去哪… · 2026/11/9 0:00:54
南方电网电工作业含金量高吗?应届生怎么考怎么干? 南方电网电工作业含金量高吗?应届生怎么考怎么干? 工地太忙根本没时间复习考试,这不是个别现象,而是很多刚毕业的工程类学生面临的现实。南方电网电工作业的证书,听起来门槛不低,但 含金量… · 2026/11/9 0:00:54
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/9 11:23:35
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/9 11:23:58
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/9 11:25:08