13. 项目大结局

至此,项目已经完成,下面我们对项目做一个总结。

前几节已将给出的项目中各个部分的说明和源代码。如果你需要项目完整的代码请点击校园信息管理系统项目源代码 下载。

校园信息管理系统项目源代码结构如下:

school_info_manager/
├── class_room.c
├── class_room.h
├── main.c
├── Makefile
├── school.c
├── school.h
├── student.h
├── students.csv
├── tools.c
└── tools.h

此项目共分为 5 个模块,对应 4 个 .c 文件,模块说明和对应文件内容如下:

以下列出的上述项目的源代码。供参考。

文件 main.c

#include <stdio.h>
#include "school.h"

int main(int argc, char *argv[])
{
    // 加载 csv 文件中的信息
    load_from_csv_file(DEFAULT_DOC_PATHNAME);
    // 进入学生信息管理主界面
    class_manager();

    return 0;
}

文件 tools.h

#ifndef __TOOLS_H
#define __TOOLS_H

/* 返回字符串s的显示宽度,中文占两个英文字符的宽度*/
int get_display_width(const char * s);

/* 将字符串s的左右两端添加空格,使其达到 width 的显示宽度,存入 target 中*/
void center_to_display_width(const char * s, int width, char * target);

#endif  // __TOOLS_H

文件 tools.c

#include <string.h>
#include "tools.h"

// 测试当前文件的编码,如果是 UTF8 编码,则中文两个字占 6 字节,如果是 GBK 则占 4 字节
static int is_utf8_code(void)
{
    if (strlen("中文") == 6)
        return 1;
    return 0;
}

/* 返回字符串s的显示宽度,中文占两个英文字符的宽度
   如:
      "abc" -->3
      "中文" --> 4
      "ABC中文" --> 7
*/
int get_display_width(const char * s)
{
    int display_width = 0;
    int is_utf8 = is_utf8_code();
    // 计算字符串 s 占用屏幕控制台终端的宽度。
    while (*s) {
        if (*s >= 0 && *s <= 127) {  // 英文编码
            display_width++;  // 英文占一个字符宽
            s++;
        } else if (is_utf8) {  // 中文 UTF8 编码
            display_width += 2;  // 中文占两个字符宽
            s += 3;  // UTF8 编码一个汉字占 3 字节内存
        } else { // 中文 GBK 编码
            display_width += 2;
            s += 2;  // GBK 编码一个汉字占 2 字节内存
        }
    }

    return display_width;
}

/* 将字符串s的左右两端添加空格,使其达到 width 的显示宽度,存入 target 中
   如:
      s = 'ABC中文', width = 10
      返回:' ABC中文  '
*/
void center_to_display_width(const char * s, int width, char * target)
{
    // 得到当前字符的显示宽度
    int s_width = get_display_width(s);
    // 计算需要补充的空格数
    int fill_blank_count = width - s_width;

    // 计算左侧需要填充的空格数
    int left_blank = fill_blank_count / 2;
    // 计算右侧需要填充的空格数
    int right_blanks = fill_blank_count - left_blank;
    // 使用指针指向 target 的内容
    char * ptar = target;
    int i;

    if (fill_blank_count < 0) {  // 宽度小于 字符串宽度
        strcpy(target, s);
        return;
    }
    // 将 target 的左侧填充 fill 空格
    for (i = 0; i < left_blank; i++, ptar++) {
        *ptar = ' ';  // 填充空格
    }
    // 将 s 追加到 target 后面
    while(*s) {
        *ptar = *s;
        s++;
        ptar++;
    }
    // 将 target 的右侧填充空格
    for (i = 0; i < right_blanks; i++, ptar++) {
        *ptar = ' ';  // 填充空格
    }
    *ptar = '\0';   // 尾零
}

文件 student.h

#ifndef __STUDENT_H
#define __STUDENT_H

#define MAX_STU_NAME_LEN (32)   // 学生姓名的最大长度

// 学生类型
typedef struct student
{
    char name[MAX_STU_NAME_LEN];  // 姓名
    int chinese_score;  // 语文成绩
    int math_score;  // 数学成绩
} student_t;

#endif  // __STUDENT_H

文件 class_room.h

#ifndef __CLASS_ROOM_H
#define __CLASS_ROOM_H

#include <stdio.h>
#include "student.h"

// 班级名称的最大长度
#define MAX_CLASS_TITLE_LEN (64)

// 定义每个班级最大学生个数。
#define MAX_STU_COUNT_IN_CLASS_ROOM  (100)

// 班级的结构体
typedef struct class_room {
    char class_title[MAX_CLASS_TITLE_LEN];  // 班级名
    student_t student[MAX_STU_COUNT_IN_CLASS_ROOM]; // 每个班级的学生
    int student_count; // 用来记录具体的学生个数
} class_room_t;


// 添加学生信息
void add_student(class_room_t *aclass);
// 修改语文成绩功能
void modify_chinese_score(class_room_t *aclass);
// 修改数学成绩功能
void modify_math_score(class_room_t *aclass);
// 删除学生信息
void del_student(class_room_t *aclass);
// 显示所有学生的信息
void list_all_student_info(class_room_t *aclass);
// 写入一个班级的学生数据
void writer_to_csv_writer(class_room_t *aclass, FILE *csv_file);
// 将此学生添加到 aclass 这个班级中。成功返回1, 失败返回 0。
int add_student_by_info(class_room_t *aclass, const char * student_name,
    int chinese_score,int math_score);
// 此函数用来管理学生数据
void student_manager(class_room_t *aclass);

#endif  // __CLASS_ROOM_H

文件 class_room.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "tools.h"
#include "student.h"
#include "class_room.h"

// 添加学生信息
void add_student(class_room_t *aclass)
{
    char student_name[MAX_STU_NAME_LEN*2];
    int chinese_score;
    int math_score;

    if (aclass->student_count >= MAX_STU_COUNT_IN_CLASS_ROOM) {
        printf("班级 %s 学生已满,如法添加学生信息\n", aclass->class_title);
        return;
    }

    printf("请输入学生姓名: ");
    fflush(stdout);
    scanf("%s", student_name);
    // fgets(student_name, sizeof(student_name), stdin);
    if (strlen(student_name) >= MAX_STU_NAME_LEN) {
        printf("学生的名字太长,添加失败!\n");
        return;
    }
    printf("请输入学生的语文成绩: ");
    fflush(stdout);
    scanf("%d", &chinese_score);
    if (chinese_score < 0 || chinese_score > 100) {
        printf("学生成绩不在合法范围内,添加失败!\n");
        return;
    }
    printf("请输入学生的数学成绩: ");
    fflush(stdout);
    scanf("%d", &math_score);
    if (math_score < 0 || math_score > 100) {
        printf("学生成绩不在合法范围内,添加失败!\n");
        return;
    }

    student_t astu;
    strcpy(astu.name, student_name);
    astu.chinese_score = chinese_score;
    astu.math_score = math_score;

    aclass->student[aclass->student_count] = astu;
    aclass->student_count++;
    printf("添加学生 %s 成功!", student_name);
}

// 修改语文成绩功能
void modify_chinese_score(class_room_t *aclass)
{
    int number = 0;
    int index;
    int new_score = 0;
    list_all_student_info(aclass);
    printf("请输入要修改语文成绩的学生的序号:");
    fflush(stdout);

    scanf("%d", &number);
    index = number - 1;
    if (index < 0 || index >= MAX_STU_COUNT_IN_CLASS_ROOM) {
        printf("您输入的序号有误,修改失败!\n");
        return;
    }
    student_t *astu = &aclass->student[index];
    printf("请输入%s的新的语文成绩: ", astu->name);
    fflush(stdout);
    scanf("%d", &new_score);
    astu->chinese_score = new_score;
    printf("修改%s的语文成绩成功!\n", astu->name);
}

// 修改数学成绩功能
void modify_math_score(class_room_t *aclass)
{
    int number = 0;
    int index;
    int new_score = 0;
    list_all_student_info(aclass);
    printf("请输入要修改数学成绩的学生的序号:");
    fflush(stdout);

    scanf("%d", &number);
    index = number - 1;
    if (index < 0 || index >= MAX_STU_COUNT_IN_CLASS_ROOM) {
        printf("您输入的序号有误,修改失败!\n");
        return;
    }
    student_t *astu = &aclass->student[index];
    printf("请输入%s的新的数学成绩: ", astu->name);
    fflush(stdout);
    scanf("%d", &new_score);
    astu->math_score = new_score;
    printf("修改%s的数学成绩成功!\n", astu->name);
}

// 删除学生信息
void del_student(class_room_t *aclass)
{
    int number = 0;
    int index;

    list_all_student_info(aclass);

    printf("请输入要修改数学成绩的学生的序号:");
    fflush(stdout);

    scanf("%d", &number);
    index = number - 1;
    if (index < 0 || index >= aclass->student_count) {
        printf("您输入的序号有错,删除失败!\n");
        return;
    }
    // 循环将 index +1 位置的数据覆盖 index 位置的数据
    for(; index < aclass->student_count-1; index++) {
        aclass->student[index] = aclass->student[index+1];
    }
    // 学生数减1
    aclass->student_count--;
}

// 显示所有学生的信息
void list_all_student_info(class_room_t *aclass)
{
    int i;
    const student_t *astu;
    char stu_name_buf[MAX_STU_NAME_LEN*2];

    printf("+------+----------------------+--------+--------+\n");
    printf("| 序号 |         姓名         |语文成绩|数学成绩|\n");
    printf("+------+----------------------+--------+--------+\n");
    for (i = 0; i < aclass->student_count; i++) {
        astu = &aclass->student[i];
        center_to_display_width(astu->name, 20, stu_name_buf);
        printf("| %4d | %s |  %4d  |  %4d  |\n",
            i+1, stu_name_buf, astu->chinese_score, astu->math_score);
    }
    if (aclass->student_count)
        printf("+------+----------------------+--------+--------+\n");
}

// 写入一个班级的学生数据
void writer_to_csv_writer(class_room_t *aclass, FILE *csv_file)
{
    int i;
    for (i = 0; i < aclass->student_count; i++) {
        student_t *astu = &aclass->student[i];
        fprintf(csv_file, "%s,%s,%d,%d\r\n", aclass->class_title,
            astu->name, astu->chinese_score, astu->math_score);
    }
}

// 将此学生添加到 aclass 这个班级中。成功返回1, 失败返回 0。
int add_student_by_info(class_room_t *aclass, const char * student_name,
    int chinese_score,int math_score)
{
    if(aclass->student_count >= MAX_STU_COUNT_IN_CLASS_ROOM)
        return 0;  // 满了
    strcpy(aclass->student[aclass->student_count].name, student_name);
    aclass->student[aclass->student_count].chinese_score = chinese_score;
    aclass->student[aclass->student_count].math_score = math_score;
    aclass->student_count++;
    return 1;
}

// 此函数用来显示操作菜单
static void show_class_menu(class_room_t *aclass)
{
    printf("        %s-班级管理\n", aclass->class_title);
    printf("+-----------------------------------+\n");
    printf("| 1) 添加学生                       |\n");
    printf("| 2) 修改学生的语文成绩             |\n");
    printf("| 3) 修改学生的数学成绩             |\n");
    printf("| 4) 删除学生                       |\n");
    printf("| 5) 列出所有学生的成绩             |\n");
    printf("| 0) 退出班级                       |\n");
    printf("+-----------------------------------+\n");
    printf("请选择:");
    fflush(stdout);
}

// 此函数用来管理学生数据
void student_manager(class_room_t *aclass)
{
    while(1) {
        int sel = 0;
        show_class_menu(aclass);
        scanf("%d", &sel);
        switch (sel)
        {
            case 1:  // 1) 添加学生
                add_student(aclass);
                break;
            case 2:  // 2) 修改学生的语文成绩
                modify_chinese_score(aclass);
                break;
            case 3:  // 3) 修改学生的数学成绩
                modify_math_score(aclass);
                break;
            case 4:  // 4) 删除学生
                del_student(aclass);
                break;
            case 5:  // 5) 列出所有学生的成绩
                list_all_student_info(aclass);
                break;
            case 0:  // 0) 退出班级
                return;
            default:
                printf("不存在的选项,请重新输入\n");
                sleep(2);  // 让程序睡眠2秒
        }
    }
}

文件 school.h

#ifndef __SCHOOL_H
#define __SCHOOL_H

// 每个学校最大的班级数量
#define MAX_CLASS_COUNT_IN_SCHOOL (50)

// 默认的文档存储路径
#define DEFAULT_DOC_PATHNAME   "./students.csv"

// 加载班级信息,成功返回 1,失败返回 0;
int load_from_csv_file(const char *path_name);
// 此函数用来管理班级数据
void class_manager(void);

#endif // __SCHOOL_H

文件 school.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include "tools.h"
#include "school.h"
#include "class_room.h"

// 用于存放班级对象,初始状态为空
static class_room_t class_rooms[MAX_CLASS_COUNT_IN_SCHOOL] = {};
static int class_room_count = 0;  // 班级的个数

// 添加班级
void add_class_room(void)
{
    char class_title[MAX_CLASS_TITLE_LEN*2];

    // 判断是否达到班级的最大数量
    if (class_room_count >= MAX_CLASS_COUNT_IN_SCHOOL) {
        printf("已经达到了编辑的最大数量!\n");
        return;
    }

    printf("请输入班级名称: ");
    fflush(stdout);
    scanf("%s", class_title);
    if (get_display_width(class_title) <= 10) {
        strcpy(class_rooms[class_room_count].class_title, class_title);
        class_room_count++;
        printf("添加班级%s成功!\n", class_title);
    } else {
        printf("添加班级失败,班级名太长!\n");
    }

    sleep(2);
}

void list_all_class_room(void)
{
    int i;
    char class_title[MAX_CLASS_TITLE_LEN*2];

    printf("+------+------------+\n");
    printf("| 序号 |  班级名称  |\n");
    printf("+------+------------+\n");
    for (i = 0; i < class_room_count; i++) {
        center_to_display_width(class_rooms[i].class_title, 10, class_title);
        printf("| %4d | %s |\n", i+1, class_title);
    }
    if (class_room_count)
        printf("+------+------------+\n");
}

// 删除班级
void del_class_room(void)
{
    int number = 0;
    int index;

    list_all_class_room();
    printf("请输入删除班级的序号: ");
    fflush(stdout);
    scanf("%d", &number);
    index = number - 1;  // 对应列表的索引
    if (index < 0 || index >= class_room_count) {
        printf("您输入的序号有误,删除失败!\n");
        sleep(2);
    }
    // 将后续编辑依次向前覆盖
    for (; index < class_room_count-1; index++) {
        class_rooms[index] = class_rooms[index+1];
    }
    class_room_count--;
    printf("删除成功!\n");
    sleep(2);
}

// 进入管理班级界面
void enter_class_manager(void)
{
    int number = 0;
    int index;
    class_room_t *aclass;

    list_all_class_room();
    printf("请输入一个要管理班级的序号: ");
    fflush(stdout);
    scanf("%d", &number);
    index = number - 1;  // 对应列表的索引
    if (index < 0 || index >= class_room_count) {
        printf("您输入的班级序号有误!\n");
        return;
    }
    aclass = &class_rooms[index];
    student_manager(aclass);
}

// 保存班级信息,成功返回 1, 失败返回 0;
int save_to_csv_file(const char *path_name)
{
    const char * header = "班级名称,学生姓名,语文成绩,数学成绩";
    FILE * file = fopen(path_name, "w");  // 打开文件
    int i;

    if (NULL == file) {
        printf("打开文件失败,保存文件失败!\n");
        return 0;
    }
    fprintf(file, "%s\r\n", header);

    for (i = 0; i < class_room_count; i++) {
        writer_to_csv_writer(&class_rooms[i], file);
    }

    fclose(file);
    return 1;
}

// 加载班级信息, 成功返回 1, 失败返回 0;
int load_from_csv_file(const char *path_name)
{
    FILE * file = fopen(path_name, "r");  // 打开文件
    // 记录之前的班级名,重复班级名的学生是同一班的学生
    char last_class_title[MAX_CLASS_TITLE_LEN] = "";
    char line_buf[100] = ""; // 读取一行数据的缓冲区。
    char *pclass_title;  // 指向班级名
    char *pstudent_name;  //  指向学生名
    char *pchinese_score;  // 指向语文成绩的字符串
    char *pmath_score;  // 指向语文成绩的字符串
    char *p;  // 用于指向每一行行数据的临时指针

    if (NULL == file) {
        printf("打开文件失败,读取文件失败!\n");
        return 0;
    }

    class_room_count = 0;  //  清空原有数据

    // 读取一行,跳过头部的第一行标题
    fgets(line_buf, sizeof(line_buf), file);
    // 每次读取一行,然后解析每一行的数据
    while(NULL != fgets(line_buf, sizeof(line_buf), file)) {
        // 解析每一行的数据
        pclass_title = line_buf;
        // 找到逗号,逗号改成班级名的尾零
        p = strstr(line_buf, ",");
        if (NULL == p)
            goto finished;
        *p = '\0';
        p++;  // 指向学生姓名
        // 指向学生姓名的起始地址。
        pstudent_name = p;
        p = strstr(p, ",");
        if (NULL == p)
            goto finished;
        *p = '\0';
        p++; // 指向语文成绩;

        // 指向学生语文成绩的起始地址。
        pchinese_score = p;
        p = strstr(p, ",");
        if (NULL == p)
            goto finished;
        *p = '\0';
        p++; // 指向语文成绩;

        // 指向学生数学成绩的起始地址。
        pmath_score = p;

        // 判断 班级名称发生改变。则说明已经是不同的班级
        if (0 != strcmp(last_class_title, pclass_title)) {
            // 如果已经超出了班级个数,则直接返回
            if (class_room_count == MAX_CLASS_COUNT_IN_SCHOOL) {
                goto finished;
            } else {
                // 初始化当前的班级信息
                strcpy(class_rooms[class_room_count].class_title, pclass_title);
                class_rooms[class_room_count].student_count = 0;
                class_room_count++;
            }
            strcpy(last_class_title, pclass_title);
        }
        //
        // 将此学生信息添加到 class_room_count-1 这个班级中
        add_student_by_info(&class_rooms[class_room_count-1],
            pstudent_name, atoi(pchinese_score), atoi(pmath_score));
        // 解析完毕,
    }
finished:
    fclose(file);
    return 1;
}

void show_school_menu(void)
{
    printf("        xxxx小学信息管理系统\n");
    printf("+-----------------------------------+\n");
    printf("| 1) 添加班级                       |\n");
    printf("| 2) 删除班级                       |\n");
    printf("| 3) 进入管理班级                   |\n");
    printf("| 4) 列出所有班级                   |\n");
    printf("| 5) 保存班级信息                   |\n");
    printf("| 6) 加载班级信息                   |\n");
    printf("| 0) 退出程序                       |\n");
    printf("+-----------------------------------+\n");
    printf("请选择:");
    fflush(stdout);  // 清空缓冲区
}
// 此函数用来管理班级数据
void class_manager(void)
{
    while (1)
    {
        int sel = 0;
        show_school_menu();
        scanf("%d", &sel);
        switch (sel)
        {
            case 1:  // 1) 添加班级
                add_class_room();
                break;
            case 2:  // 2) 删除班级
                del_class_room();
                break;
            case 3:  // 3) 进入管理班级
                enter_class_manager();
                break;
            case 4:  // 4) 列出所有班级
                list_all_class_room();
                break;
            case 5:  // 5) 保存班级信息
                save_to_csv_file(DEFAULT_DOC_PATHNAME);
                break;
            case 6:  // 6) 加载班级信息
                load_from_csv_file(DEFAULT_DOC_PATHNAME);
                break;
            case 0:  // 0) 退出程序
                return;
            default:
                printf("不存在的选项,请重新输入\n");
                sleep(2);
        }
    }
}

下面总结一下项目的优缺点:

优点

缺点

缺点的改进型存储方法

班级表:

classes.csv

格式

班级ID
班级名称
1
一年1班
2
一年2班

学生表

student.csv

格式

班级ID
学生姓名
语文成绩
数学成绩
1
张三
100
99
1
李四
98
97
2
王五
96
95
2
赵六
94
93

改进为关系型数据库存储

班级表(classes)

班级ID
班级名称
1
一年1班
2
一年2班

学生表(student)

学生ID
学生姓名
班级ID
1
张三
1
2
李四
1
3
王五
2
4
赵六
2

课程表(subject)

课程ID
课程
1
语文
2
数学

成绩表(score)

学生ID
课程ID
成绩
1
1
100
1
2
99
2
1
98
2
2
97
...
...
...

使用上述四个数据表来存储学生信息,如果再需要其他信息时可以通过数据表关系运算计算得到,这样将更有利于程序的扩展,也可让程序更具有可维护性。

校园信息管理系统项目-全剧终