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 文件,模块说明和对应文件内容如下:
- 学校模块,负责班级的管理,相关文件:
school.c、school.h; - 班级模块,负责班级内学生的管理,相关文件:
class_room.c、class_room.h; - 学生模块,只有一个文件
student.h,其中定义了学生的信息; - 工具模块,用于计算一个字符串占用的显示宽度和左右填充空格等功能,相关文件:
tools.c、tools.h; - 主模块,用于调用其他模块,相关文件:
main.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);
}
}
}
下面总结一下项目的优缺点:
优点
- 模块化设计,设计思路清晰。
- 代码量少,功能齐全。
- 数据能够长期存储。
- 数据用电子表格软件(如:wps)可读。
缺点
- 学生只有两门成绩,难于扩展。
- 只适合一个学校的单机使用。
- 如果班级里面没有学生,则保存是会丢失班级信息,导致加载时缺少班级信息。
- 班级重名,保存后加载会合并班级。
- 没有异常处理机制,程序崩溃,数据丢失。
缺点的改进型存储方法
班级表:
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
...
...
...
使用上述四个数据表来存储学生信息,如果再需要其他信息时可以通过数据表关系运算计算得到,这样将更有利于程序的扩展,也可让程序更具有可维护性。
