第一周软件开发与创新课程作业
一.来源:来自同年级同学的学生信息管理系统期末大作业项目。
二.运行环境+运行结果的截图:
1.源代码:
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define MAX_NUM 50
#define FILE_NAME "info.dat"
float line;
int num = 0;
int k = 0;
int n = 0;
struct Student
{
int id;
char name[MAX_NUM];
int sex;
int age;
float score;
char addr[100];
struct Student* next;
};
Student* head = NULL;
float score[1000005];
int id[1000005];
////函数声明////
void print(char str, int n);
void main_menu_display();
void info_menu_display();
void score_menu_display();
void file_menu_display();
void info_manage();
void add_student();
void delete_student();
void modify_student();
void find_student();
void view_student();
void score_menu_display();
void score_manage();
void file_manage();
void Check_in();
void score_ranking();
void List_viewing();
void scholarship_score_line();
void read_file();
void write_file();
////main////
int main()
{
int choice;
do
{
system("cls"); // 清屏
main_menu_display();
printf("请选择:\n");
scanf("%d", &choice);
getchar();
switch (choice)
{
case 1:
info_manage();
break;
case 2:
score_manage();
break;
case 3:
file_manage();
break;
case 4:
printf("正在退出程序...\n");
break;
default:
printf("请输入正确的选项\n");
}
Sleep(1000);
} while (choice != 4);
return 0;
}
void print(char str, int n)
{
for (int i = 1; i <= n; i++)
printf("%c", str);
}
void main_menu_display()
{
print('*', 100);
printf("\n");
print(' ', 41 + 4);
printf("学生管理系统");
printf("\n");
print(' ', 41);
printf("1 学生基本信息管理");
printf("\n");
print(' ', 41);
printf("2 学生成绩信息管理");
printf("\n");
print(' ', 41);
printf("3 文件操作");
printf("\n");
print(' ', 41);
printf("4 退出");
printf("\n");
print('*', 100);
printf("\n");
return;
}
void info_menu_display()
{
print('*', 100);
printf("\n");
print(' ', 42);
printf("学生基本信息管理");
printf("\n");
print(' ', 41);
printf("1 添加新学生信息");
printf("\n");
print(' ', 41);
printf("2 删除学生信息");
printf("\n");
print(' ', 41);
printf("3 修改学生信息");
printf("\n");
print(' ', 41);
printf("4 查看所有学生信息");
printf("\n");
print(' ', 41);
printf("5 查找学生信息");
printf("\n");
print(' ', 41);
printf("6 返回上一级");
printf("\n");
print('*', 100);
printf("\n");
return;
}
void info_manage()
{
int choice;
do
{
system("cls"); // 清屏
info_menu_display();
printf("请选择:\n");
scanf("%d", &choice);
getchar();
switch (choice)
{
case 1:
add_student();
break;
case 2:
delete_student();
break;
case 3:
modify_student();
break;
case 4:
view_student();
break;
case 5:
find_student();
break;
case 6:
printf("正在返回上一级...");
break;
default:
printf("请输入正确的选项\n");
}
Sleep(1000);
} while (choice != 6);
}
void add_student()
{
Student* newStudent = (Student*)malloc(sizeof(Student)); // 为新学生分配内存
if (!newStudent)
{
printf("内存分配失败,请重试\n");
return;
}
printf("请输入学号: ");
scanf("%d", &newStudent->id);
getchar();
printf("请输入姓名: ");
scanf("%s", &newStudent->name);
printf("请输入年龄: ");
scanf("%d", &newStudent->age);
printf("请输入性别:(1为男,0为女) ");
scanf("%d", &newStudent->sex);
printf("请输入家庭地址: ");
scanf("%s", &newStudent->addr);
newStudent->score = 0.00;
newStudent->next = head;
head = newStudent;
printf("成功添加新学生信息\n");
n++;
}
void delete_student()
{
int id;
printf("请输入待删除学生的学号:\n");
scanf("%d", &id);
Student* current = head;
Student* prev = NULL;
while (current != NULL)
{
if (current->id == id)
{
if (prev == NULL)
{ // 删掉头部node
head = current->next;
}
else
{ // 删掉中部node
prev->next = current->next; // 相当于跳过了current节点
}
free(current);
printf("已成功删除\n");
return;
}
prev = current;
current = current->next;
}
printf("未找到对应学号的学生\n");
return;
}
void modify_student()
{
int id;
printf("请输入待修改学生的学号:\n");
scanf("%d", &id);
Student* current = head;
// Student *prev=NULL;
while (current != NULL)
{
if (current->id == id)
{
printf("请输入新姓名: ");
scanf("%s", ¤t->name);
printf("请输入新年龄: ");
scanf("%d", ¤t->age);
printf("请输入新性别:(1为男,0为女) ");
scanf("%d", ¤t->sex);
printf("请输入新家庭地址: ");
scanf("%s", ¤t->addr);
printf("已成功修改\n");
return;
}
// prev=current;
current = current->next;
}
printf("未找到对应学号的学生\n");
return;
}
void find_student()
{
int id;
printf("请输入待查找学生的学号:\n");
scanf("%d", &id);
Student* current = head;
// Student *prev=NULL;
while (current != NULL)
{
if (current->id == id)
{
printf("\n学号\t姓名\t年龄\t性别\t家庭住址\n");
print('*', 100);
printf("\n");
printf("%d\t%s\t%d\t%s\t%s\n", current->id, current->name, current->age, current->sex ? "男" : "女", current->addr);
printf("任意键以继续\n");
getchar();
getchar();
return;
}
// prev=current;
current = current->next;
}
printf("未找到对应学号的学生\n");
return;
}
void view_student()
{
if (head == NULL)
{
printf("尚未添加任何学生信息\n");
return;
}
Student* current = head;
printf("\n学号\t姓名\t年龄\t性别\t家庭住址\t绩点\n");
print('*', 100);
printf("\n");
while (current != NULL)
{
printf("%d\t%s\t%d\t%s\t%s\t%.2f\n", current->id, current->name, current->age, current->sex ? "男" : "女", current->addr, current->score);
current = current->next;
}
printf("任意键以继续\n");
getchar();
}
void score_menu_display()
{
print('*', 100);
printf("\n");
print(' ', 42);
printf("学生成绩信息管理");
printf("\n");
print(' ', 41);
printf("1 学生成绩登记");
printf("\n");
print(' ', 41);
printf("2 查看某同学绩点排名");
printf("\n");
print(' ', 41);
printf("3 设置奖学金绩点分数线");
printf("\n");
print(' ', 41);
printf("4 查看奖学金名单");
printf("\n");
print(' ', 41);
printf("5 返回上一级");
printf("\n");
print('*', 100);
printf("\n");
return;
}
void score_manage()
{
int choice;
do
{
system("cls");
score_menu_display();
printf("请选择:\n");
scanf("%d", &choice);
getchar();
switch (choice)
{
case 1:
Check_in();
break;
case 2:
score_ranking();
break;
case 3:
scholarship_score_line();
break;
case 4:
List_viewing();
break;
case 5:
printf("正在返回上一级...");
break;
default:
printf("请输入正确的选项");
}
Sleep(1000);
} while (choice != 5);
return;
}
void Check_in()
{
int id;
printf("请输入要登记的学生的学号:\n");
scanf("%d", &id);
Student* current = head;
// Student *prev=NULL;
while (current != NULL)
{
if (current->id == id)
{
printf("请输入该登记学生的成绩:\n");
scanf("%f", ¤t->score);
printf("已成功登记\n");
return;
}
// prev=current;
current = current->next;
}
printf("未找到对应学号的学生\n");
return;
}
void score_ranking()
{
if (head == NULL)
{
printf("尚未添加任何学生信息\n");
return;
}
Student* current = head;
k = 0;
memset(score, 0, sizeof(score));
memset(id, 0, sizeof(id));
while (current != NULL)
{
score[k] = current->score;
id[k] = current->id;
k++;
current = current->next; // 使current指向下一个节点//
}
int flag = 1;
while (flag == 1)
{
flag = 0;
for (int j = 0; j < k - 1; j++)
{
if (score[j] < score[j + 1])
{
flag = 1;
int t1 = score[j];
score[j] = score[j + 1];
score[j + 1] = t1;
int t2 = id[j];
id[j] = id[j + 1];
id[j + 1] = t2;
}
}
}
printf("学生成绩排名:\n");
printf("\n学号\t姓名\t绩点\n");
print('*', 100);
printf("\n");
for (int i = 0; i < k; i++)
{
Student* current = head;
while (current != NULL)
{
if (current->id == id[i])
printf("%d\t%s\t%.2f\n", current->id, current->name, score[i]);
current = current->next;
}
}
printf("任意键以继续\n");
getchar();
}
// 设置奖学金绩点分数线//
void scholarship_score_line()
{
printf("请输入奖学金绩点分数线: ");
scanf("%f", &line);
printf("奖学金绩点分数线已设置为:%.2f\n", line);
}
void List_viewing()
{
printf("当前分数线为%.2f\n", line);
printf("\n学号\t姓名\t绩点\n");
print('*', 100);
printf("\n");
for (int i = 0; i < k; i++)
{
if (score[i] >= line)
{
Student* current = head;
while (current != NULL)
{
if (current->id == id[i])
printf("%d\t%s\t%.2f\n", current->id, current->name, score[i]);
current = current->next;
}
}
}
printf("按任意键以继续\n");
getchar();
return;
}
void file_manage()
{
int choice;
do
{
system("cls"); // 清屏
file_menu_display();
printf("请选择:\n");
scanf("%d", &choice);
getchar();
switch (choice)
{
case 1:
read_file();
break;
case 2:
write_file();
break;
case 3:
printf("正在返回上一级...");
break;
default:
printf("请输入正确的选项\n");
}
Sleep(1000);
} while (choice != 3);
}
void file_menu_display()
{
print('*', 100);
printf("\n");
print(' ', 41 + 4);
printf("文件操作");
printf("\n");
print(' ', 41);
printf("1 从文件读入学生信息");
printf("\n");
print(' ', 41);
printf("2 导出学生信息为文件");
printf("\n");
print(' ', 41);
printf("3 返回上一级");
printf("\n");
print('*', 100);
printf("\n");
return;
}
void read_file()
{
FILE* file = fopen(FILE_NAME, "rb");
if (!file)
{
printf("文件夹内没有找到对应文件\n");
return;
}
Student* newStudent;
while ((newStudent = (Student*)malloc(sizeof(Student))) != NULL && fread(newStudent, sizeof(Student), 1, file) == 1)
{
newStudent->next = head;
head = newStudent;
}
fclose(file);
printf("成功导入文件信息\n");
}
void write_file()
{
FILE* file = fopen(FILE_NAME, "wb");
if (!file)
{
printf("未能成功打开文件\n");
return;
}
Student* current = head;
while (current != NULL)
{
fwrite(current, sizeof(Student), 1, file);
current = current->next;
}
fclose(file);
printf("成功导出文件信息\n");
}
2.运行环境:

3.运行结果:
添加:

修改:


查找:

删除:


成绩添加


成绩查看

奖学金设置


文件操作:
先导出

在删除所有学生

接着从文件读入学生信息


边界情况


三.主要问题列表,针对问题的改善或者重构思路
1.主要问题:scanf语法错误 (scanf("%s", &newStudent->name);),以及无长度限制的%s输入危险,可能导致缓冲区溢出。(scanf("%s", newStudent->addr);)
改善:(1)添加缓冲区清理函数
void clear_input_buffer() {
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
把它添加到add_student()、view_student()、modify_student()、find_student()中;
(2)修改输入,加上长度限制,修改语法错误,删去&
printf("请输入姓名: ");
scanf("%49s", newStudent->name);
clear_input_buffer();
printf("请输入家庭地址: ");
scanf("%99s", newStudent->addr);
clear_input_buffer();
2.主要问题:内存泄漏问题,每次从文件读取都会丢失原有链表的内存,程序退出时没有释放链表
改善:
点击查看代码
// 修复 read_file() 函数
void read_file()
{
// 先释放原有链表
Student* current = head;
while (current != NULL) {
Student* temp = current;
current = current->next;
free(temp);
}
head = NULL;
n = 0; // 重置计数器
FILE* file = fopen(FILE_NAME, "rb");
if (!file)
{
printf("文件夹内没有找到对应文件\n");
return;
}
Student* newStudent;
while ((newStudent = (Student*)malloc(sizeof(Student))) != NULL)
{
if (fread(newStudent, sizeof(Student), 1, file) != 1)
{
free(newStudent);
break;
}
newStudent->next = head;
head = newStudent;
n++; // 更新计数器
}
fclose(file);
printf("成功导入文件信息\n", n);
}
// 增加程序退出时的内存释放
void free_all_memory() {
Student* current = head;
while (current != NULL) {
Student* temp = current;
current = current->next;
free(temp);
}
head = NULL;
n = 0;
k = 0;
}
// 修改main函数
case 4:
printf("正在退出程序...\n");
free_all_memory(); // 释放内存
break;
3.主要问题:缺少数据验证,如年龄可输入负数、300岁,成绩可输入负数、超过4.0,学号可重复
改善:添加验证函数
点击查看代码
//数据验证函数
int is_id_exists(int id) {
Student* current = head;
while (current != NULL) {
if (current->id == id) {
return 1; // 学号已存在
}
current = current->next;
}
return 0; // 学号不存在
}
int validate_age(int age) {
return age > 0 && age < 101; // 年龄在1-100之间
}
int validate_sex(int sex) {
return sex == 0 || sex == 1; // 性别只能是0或1
}
int validate_score(float score) {
return score >= 0.0 && score <= 4.0; // 绩点在0-4之间
}
// 修改 add_student(),添加数据验证
void add_student()
{
Student* newStudent = (Student*)malloc(sizeof(Student));
if (!newStudent)
{
printf("内存分配失败,请重试\n");
return;
}
//学号唯一性验证
printf("请输入学号: ");
scanf("%d", &newStudent->id);
clear_input_buffer();
if (is_id_exists(newStudent->id)) {
printf("学号已存在,添加失败\n");
free(newStudent);
return;
}
printf("请输入姓名: ");
scanf("%49s", newStudent->name);
clear_input_buffer();
//年龄范围验证
do {
printf("请输入年龄: ");
scanf("%d", &newStudent->age);
clear_input_buffer();
if (!validate_age(newStudent->age)) {
printf("年龄必须在1-119之间,请重新输入\n");
}
} while (!validate_age(newStudent->age));
printf("请输入性别:(1为男,0为女) ");
scanf("%d", &newStudent->sex);
clear_input_buffer();
printf("请输入家庭地址: ");
scanf("%99s", newStudent->addr);
clear_input_buffer();
newStudent->score = 0.00;
newStudent->next = head;
head = newStudent;
printf("成功添加新学生信息\n");
n++;
}
// 修改 modify_student(),同上
void modify_student()
{
int id;
printf("请输入待修改学生的学号:\n");
scanf("%d", &id);
clear_input_buffer();
Student* current = head;
while (current != NULL)
{
if (current->id == id)
{
printf("请输入新姓名: ");
scanf("%49s", current->name);
clear_input_buffer();
// 年龄范围验证
do {
printf("请输入新年龄: ");
scanf("%d", ¤t->age);
clear_input_buffer();
if (!validate_age(current->age)) {
printf("年龄必须在1-119之间,请重新输入\n");
}
} while (!validate_age(current->age));
printf("请输入新性别:(1为男,0为女) ");
scanf("%d", ¤t->sex);
clear_input_buffer();
printf("请输入新家庭地址: ");
scanf("%99s", current->addr);
clear_input_buffer();
printf("已成功修改\n");
return;
}
current = current->next;
}
printf("未找到对应学号的学生\n");
}
// 修改 Check_in(),同上
void Check_in()
{
int id;
printf("请输入要登记的学生的学号:\n");
scanf("%d", &id);
clear_input_buffer();
Student* current = head;
while (current != NULL)
{
if (current->id == id)
{
//成绩范围验证
do {
printf("请输入该登记学生的成绩(0-4之间):\n");
scanf("%f", ¤t->score);
clear_input_buffer();
if (!validate_score(current->score)) {
printf("成绩必须在0-4之间,请重新输入\n");
}
} while (!validate_score(current->score));
printf("已成功登记\n");
return;
}
current = current->next;
}
printf("未找到对应学号的学生\n");
}
// 修改 delete_student(),同上
void delete_student()
{
int id;
printf("请输入待删除学生的学号:\n");
scanf("%d", &id);
clear_input_buffer();
//检查学号是否存在
if (!is_id_exists(id)) {
printf("学号 %d 不存在,删除失败\n", id);
return;
}
Student* current = head;
Student* prev = NULL;
while (current != NULL)
{
if (current->id == id)
{
if (prev == NULL)
{
head = current->next;
}
else
{
prev->next = current->next;
}
free(current);
n--;
printf("已成功删除\n");
return;
}
prev = current;
current = current->next;
}
}
// 修改 find_student(),同上
void find_student()
{
int id;
printf("请输入待查找学生的学号:\n");
scanf("%d", &id);
clear_input_buffer();
//检查学号是否存在
if (!is_id_exists(id)) {
printf("学号 %d 不存在\n", id);
return;
}
Student* current = head;
while (current != NULL)
{
if (current->id == id)
{
printf("\n学号\t姓名\t年龄\t性别\t家庭住址\n");
print('*', 100);
printf("\n");
printf("%d\t%s\t%d\t%s\t%s\n", current->id, current->name, current->age, current->sex ? "男" : "女", current->addr);
printf("按回车键以继续\n");
clear_input_buffer();
getchar();
return;
}
current = current->next;
}
}
// 修改 scholarship_score_line(),添加分数线范围验证
void scholarship_score_line()
{
//分数线范围验证
do {
printf("请输入奖学金绩点分数线(0-4之间): ");
scanf("%f", &line);
clear_input_buffer();
if (line < 0 || line > 4) {
printf("分数线必须在0-4之间,请重新输入\n");
}
} while (line < 0 || line > 4);
printf("奖学金绩点分数线已设置为:%.2f\n", line);
}
4.主要问题:float score[1000005]和int id[1000005]内存浪费严重,数组是全局的,程序运行期间一直占用内存,并且链表已经存储了所有学生信息,又用两个大数组重复存储学号和成绩造成数据冗余
改善:
点击查看代码
float line;
int num = 0;
int k = 0;
int n = 0;
//删除
//float score[1000005];
//int id[1000005];
//修改score_ranking函数
void score_ranking()
{
if (head == NULL)
{
printf("尚未添加任何学生信息\n");
return;
}
// 统计学生数量
int count = 0;
Student* current = head;
while (current != NULL)
{
count++;
current = current->next;
}
// 动态分配临时数组
float* score = (float*)malloc(count * sizeof(float));
int* id = (int*)malloc(count * sizeof(int));
if (score == NULL || id == NULL)
{
printf("内存不足,无法显示排名\n");
free(score);
free(id);
return;
}
current = head;
int k = 0;
while (current != NULL)
{
score[k] = current->score;
id[k] = current->id;
k++;
current = current->next;
}
// 冒泡排序
int flag = 1;
while (flag == 1)
{
flag = 0;
for (int j = 0; j < k - 1; j++)
{
if (score[j] < score[j + 1])
{
flag = 1;
float t1 = score[j];
score[j] = score[j + 1];
score[j + 1] = t1;
int t2 = id[j];
id[j] = id[j + 1];
id[j + 1] = t2;
}
}
}
printf("学生成绩排名:\n");
printf("\n学号\t姓名\t绩点\n");
print('*', 100);
printf("\n");
for (int i = 0; i < k; i++)
{
Student* current = head;
while (current != NULL)
{
if (current->id == id[i])
printf("%d\t%s\t%.2f\n", current->id, current->name, score[i]);
current = current->next;
}
}
free(score);
free(id);
printf("任意键以继续\n");
getchar();
//修改 List_viewing函数
void List_viewing()
{
if (head == NULL)
{
printf("暂无学生信息\n");
return;
}
printf("当前分数线为%.2f\n", line);
printf("\n学号\t姓名\t绩点\n");
print('*', 100);
printf("\n");
Student* current = head;
while (current != NULL)
{
if (current->score >= line)
{
printf("%d\t%s\t%.2f\n", current->id, current->name, current->score);
}
current = current->next;
}
printf("按任意键以继续\n");
getchar();
}
}
四.重构的软件的测试截图
1.验证系统是否能阻止学号重复

2.年龄验证

3.成绩验证

4.验证超长输入是否导致缓冲区溢出


5.排名功能是否动态分配内存 并且排名从高到低


五.总结:
这次修改学生管理系统,我主要处理了四个问题:数据验证、scanf输入错误、内存泄漏,还有那个超大数组。说实话,每个问题改起来的感觉都不一样,让我对写代码这件事有了很多新的认识。
数据验证这个改起来最直观。年龄不能超过120、成绩只能在0到4之间、学号不能重复,这些都是明摆着的规则,逻辑特别清晰。但真动手改的时候才发现直接返回显得太生硬,让用户重新输入吧又要加循环,还得小心别写成死循环。就这一个选择,我纠结了好一会儿。
scanf语法错误和缓冲区溢出这个问题,现在回头看有点哭笑不得。那个多余的&,我自己写的时候有时也是会忘。还有不加长度限制的%s,平时测试输几个正常名字没问题,但万一谁手抖敲了一长串,程序直接就崩了。改的时候要挨个检查每个数组多大——name是50就用%49s,addr是100就用%99s,留一个位置给结束符。这种细节特别磨人,但漏一个就是隐患。
内存泄漏是我改得最痛苦的地方。原代码从文件读数据时,新数据直接覆盖上去,旧链表就没人管了,内存一直在那儿占着不释放。程序跑一会儿可能没问题,但要是反复读写文件,内存占用就蹭蹭往上涨。改这个的时候我头发都快薅掉了——不只是简单加个free就行,要考虑malloc失败了怎么办?文件读到一半出错了怎么办?用户突然退出怎么办?而且计数器n还得在增删改查各种操作里保持一致。我第一次意识到,资源管理不是配个对就完事,而是要管好整个生命周期。
超大数组这个问题最有意思。原代码开头定义了两个全局数组float score[1000005]和int id[1000005],改这个的时候我纠结了一下,是直接把数组改小点凑合用,还是彻底重构成动态分配。最后选了后者,长痛不如短痛。但这一改就牵扯大了,两个函数都要动,score_ranking()要改成动态分配内存,List_viewing()干脆重写,不再依赖排序后的数组,直接遍历链表判断。改完发现还挺爽的,不仅内存省了,两个功能还解耦了——以前必须先排名才能看奖学金名单,现在随时看都行,数据都是最新的。
回过头看这四个问题,我感觉自己像打怪升级一样。语法错误是最初级的,细心就能避免;安全漏洞需要有点意识,养成好习惯就行;内存泄漏就要动点脑筋了,得理解程序怎么运行的;超大数组这个层面,已经开始触及架构设计的问题了。作为学生,平时交作业只要功能跑通就行,但这次改代码让我明白,真正的工程不是这样的。代码不仅要能跑,还要跑得稳、跑得久、资源用得省。每个看似不起眼的问题,背后都可能藏着一堆连锁反应。改完这版代码,虽然累,但下次自己写的时候,应该能少踩不少坑。

浙公网安备 33010602011771号