对同学大一第一学期C语言期末课程设计的修改

该项目是一位同学大一第一学期的时候写的一个C语言期末课程设计,经过了一年的学习,不论是对程序设计的理解还是经验都有所提升,现在回过去看以前的代码,存在不少值得关注和修改的地方。

原题如下:

1.要求实现学生信息的查找、添加、删除、修改、浏览、保存、从文件读取、查看奖学金信息8个功能,每个功能模块均能实现随时从模块中退出,而且可以选择不同的方式实现所需功能,从而完成一个学生管理系统所需功能。
2.要使用结构体来实现对学生信息的存储。
3.可使用链表或数组来实现对学生信息的查找、添加、删除、修改、浏览等操作。
4.使用文件完成数据的存储与读取,要求每次运行某个功能模块时将数据读入结构体中,并给用户提供保存选项,可以将结构体中的数据保存在文件中。

其中,该同学对结构体的定义如下:

点击查看代码
typedef struct Student {
    int id;
    char name[50]; 
    float grade; 
    char scholarship[50]; 
    struct Student *next; 
} Student;
完整程序代码不表,所有注释不做摘录。

本次优化暂不考虑避免输入类型错误,在开始测试原始程序前,首先对所涉及的变量类型进行定义:

变量 类型
id 3位整数,且不以0打头
name[50] 无空格中文字符
grade 0-100的整数
name[50] “一等奖”“二等奖”“三等奖”“无”
以上变量类型定义仅为统一测试流程,提升测试效率。

为节约篇幅,测试程序过程中,主菜单仅保留第一次出现的完整界面,其余出现仅保留一行*号以示分割。

首先查看添加学生功能的代码:(删去了分配内存空间失败时的抛出代码)

点击查看代码
void addStudent() {
Student *new_student = (Student *)malloc(sizeof(Student));

    printf("输入学生ID: ");
    scanf("%d", &new_student->id);
    printf("输入学生姓名: ");
    scanf("%s", new_student->name);
    printf("输入学生成绩: ");
    scanf("%f", &new_student->grade);
    printf("输入奖学金信息: ");
    scanf("%s", new_student->scholarship);

    new_student->next = NULL;

    if (head == NULL) {
        head = new_student;
    } else {
        Student *temp = head;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = new_student;
    }
}

此段代码在输入时不对学生ID进行检查,因此可能存在一个ID对应多个学生的情况,例如:

点击查看代码
*****************************
学生信息管理系统
1. 添加学生
2. 删除学生
3. 修改学生信息
4. 浏览学生信息
5. 保存学生信息
6. 从文件读取学生信息
7. 查看奖学金信息
8. 批量添加学生信息
0. 退出
*****************************
请选择操作:1
输入学生ID: 101
输入学生姓名: 无声铃鹿
输入学生成绩: 90
输入奖学金信息: 一等奖
*****************************
请选择操作:4
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖
*****************************
请选择操作:5
学生信息已保存
*****************************
请选择操作:6
学生信息已加载
*****************************
请选择操作:4
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖

因此需要增加代码,在输入重复时抛出异常。为使代码更加人性化,在输入ID后立刻进行查重。在scanf("%d", &new_student->id);后增加以下代码块:

点击查看代码
Student *temp = head;
int isDuplicate = 0; 
while (temp != NULL) {
    if (temp->id == new_student->id) {
        isDuplicate = 1; 
        break;
    }
    temp = temp->next;
}
if (isDuplicate) {
    printf("错误:ID已存在\n");
    return; 
        }

其中变量isDuplicate用于指示是否发生重复。添加代码后,在输入重复的ID时,将会提示“错误:ID已存在”,而不会记录重复的ID,如下所示:

点击查看代码
请选择操作:1
输入学生ID: 101
错误:ID已存在

接下来查看读取文件代码:

点击查看代码
void loadStudents() {
    FILE *file = fopen("students.dat", "rb");
    if (file == NULL) {
        printf("文件打开失败\n");
        return;
    }
    Student *temp;
    while (1) {
        temp = (Student *)malloc(sizeof(Student));
        if (fread(temp, sizeof(Student), 1, file) == 0) {
            free(temp);
            break;
		}
        temp->next = NULL;
        appendToTail(temp); 
    }

    fclose(file);
    printf("学生信息已加载\n");
}

在当前代码中,读取文件会追加在内存已有的数据之后,可能导致重复,如下操作:

点击查看代码
*****************************
学生信息管理系统
1. 添加学生
2. 删除学生
3. 修改学生信息
4. 浏览学生信息
5. 保存学生信息
6. 从文件读取学生信息
7. 查看奖学金信息
8. 批量添加学生信息
0. 退出
*****************************
请选择操作:1
输入学生ID: 101
输入学生姓名: 无声铃鹿
输入学生成绩: 90
输入奖学金信息: 一等奖
*****************************
请选择操作:4
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖
*****************************
请选择操作:5
学生信息已保存
*****************************
请选择操作:6
学生信息已加载
*****************************
请选择操作:4
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖

因此增加函数freeStudents()释放内存中的数据:

点击查看代码
void freeStudents() {
    Student *temp = head;
    while (temp != NULL) {
        Student *next = temp->next; 
        free(temp); 
        temp = next; 
    }
    head = NULL;
}

同时在if(file == NULL){}后,增加一行freeStudents()以清理内存。
再次进行测试,测试结果如下:

点击查看代码
*****************************
学生信息管理系统
1. 添加学生
2. 删除学生
3. 修改学生信息
4. 浏览学生信息
5. 保存学生信息
6. 从文件读取学生信息
7. 查看奖学金信息
8. 批量添加学生信息
0. 退出
*****************************
请选择操作:1
输入学生ID: 101
输入学生姓名: 无声铃鹿
输入学生成绩: 90
输入奖学金信息: 一等奖
*****************************
请选择操作:4
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖
*****************************
请选择操作:5
学生信息已保存
*****************************
请选择操作:6
学生信息已加载
*****************************
请选择操作:4
ID: 101, 姓名: 无声铃鹿, 成绩: 90.00, 奖学金: 一等奖

在逆向软件重新开发的过程中,遇到了不少难点。
第一是不同人的代码缩进的风格不同。我同学的代码风格以

if(){
    ……
}

为主,而我个人习惯于使用

if()
{
    ……
}

进行代码块标记。(“{”位置不同)

第二是变量及函数命名的风格不同。我个人习惯在不同单词中以“_”进行连接,而不是仅仅依赖于字母的大小写进行单词的划分。例如代码中的appendToTail()函数,我倾向于写作append_To_Tail()
以上两点都会影响代码的可读性,并且不同风格若交替使用,将会极大降低后续的可维护性。因此在代码二次开发的过程中,选择了维持原代码的缩进和命名风格。
其次,尽管原代码带有较为详尽的注释,但在链表的操作习惯有较大不同,需要额外进行分析原作者对链表的操作方式。尤其是在使用了自己所不熟悉的链表插入方式时。对链表操作的分析一定程度上这也降低了程序维护的效率。

posted @ 2025-02-26 09:28  不允许使用的关键词  阅读(63)  评论(0)    收藏  举报