关于学生信息管理系统的优化
优化1:更改引导用户创建数据库的方案
FILE *fp = fopen(file_name, "r");
char s[100];
int n;
if (fp == NULL) {
printf("未检测到数据库,请问是否创建(y / n)?");
scanf("%s", s);
printf("\n");
if(s[0] == 'y') {
printf("Please enter how many data items there are in total?(请输入共有多少项数据?) : ");
scanf("%d", &n);
printf("\n");
FILE *p = fopen(file_name, "w");
fclose(p);
for (int i = 0; i < n; i++) {
create_file();
}
}
}
else {
stu_cnt = read_from_file(stu_arr);
}
fclose(fp);
利用读的特性,来检查数据库是否存在,如果不存在则引导用户创建数据库。
优化2:实现数据的单条修改操作
之前的方法是利用w打开文件使文件清空,然后写入更新后的数据。
优化后实现数据的单条修改。
typedef struct Student {
long offset; //记录数据在文件中的位置
char name[20];
int age;
int class;
float height;
} Student;
更改结构体,增加记录数据在文件中位置的变量offset。
int read_from_file(Student *arr) {
int i = 0;
FILE *fp = fopen(file_name, "r");
if (fp == NULL) return i;
while (1) {
long offset = ftell(fp);
if (fscanf(fp, "%s", arr[i].name) == EOF) break;
fscanf(fp, "%d%d%f",
&arr[i].age,
&arr[i].class,
&arr[i].height
);
arr[i].offset = offset;
fgetc(fp); //吞掉换行符
i += 1;
}
fclose(fp);
return i;
}
在从文件中读数据的代码段中维护offset的值。
注意细节:当读取完一行数据后,文件指针的位置位于一行数据末尾的换行符那里。需要用fgetc()来吞掉换行符。
void student_add() {
printf("add new item : (name, age, class, height)\n");
printf("mysql > ");
scanf("%s%d%d%f",
stu_arr[stu_cnt].name,
&stu_arr[stu_cnt].age,
&stu_arr[stu_cnt].class,
&stu_arr[stu_cnt].height
);
output_arr_file(stu_arr + stu_cnt, 1);
stu_cnt += 1;
int n = printf("add a astudent success!\n");
printf("\n");
for (int i = 0; i < n; i++) {
printf("-");
}
printf("\n\n");
return ;
}
那么如何维护在添加学生时数据的偏移量信息呢 ?
long output_arr_file(Student *arr, int n) {
FILE *fp = fopen(file_name, "a");
fseek(fp, 0, SEEK_END); //设置指针在文件的末尾
long offset = ftell(fp);
for (int i = 0; i < n; i++) {
fprintf(fp, "%s %d %d %.2f\n",
arr[i].name, arr[i].age,
arr[i].class, arr[i].height
);
fclose(fp);
return offset;
}
通过修改output_arr_file函数的返回值来维护偏移量信息。
void student_delete() {
if (stu_cnt == 0) {
printf("There is no student.\n");
return ;
}
int id;
student_list();
do {
printf("delete id : ");
scanf("%d", &id);
} while (id < 0 || id >= stu_cnt);
printf("confirm (y / n) : ");
fflush(stdin);
char s[100];
scanf("%s", s);
if(s[0] != 'y') return ;
for (int i = id + 1; i < stu_cnt; i++) {
stu_arr[i - 1] = stu_arr[i];
}
stu_cnt -= 1;
restore_data_to_file(stu_arr, stu_cnt);
return ;
}
for (int i = id + 1; i < stu_cnt; i++) {
stu_arr[i - 1] = stu_arr[i];
}
在删除学生信息的这段代码将会导致偏移量信息的错误。
for (int i = id + 1; i < stu_cnt; i++) {
long offset = stu_arr[i - 1].offset;
stu_arr[i - 1] = stu_arr[i];
stu_arr[i - 1].offset = offset;
}
改为不更新数据偏移量。
void student_modify() {
int id;
student_list();
do {
printf("modify id : ");
scanf("%d", &id);
} while (id < 0 || id >= stu_cnt);
printf("modify the item : (name, age, class, height)\n");
scanf("%s%d%d%f",
stu_arr[id].name,
&stu_arr[id].age,
&stu_arr[id].class,
&stu_arr[id].height
);
restore_data_to_file(stu_arr, stu_cnt);
return ;
}
当前代码为清空文件并更新数据,但不会更新偏移量。
实现一个方法来维护数据的偏移量。
void modify_data_to_file(Student *stu) {
FILE *fp = fopen(file_name, "r+");
fseek(fp, stu->offset, SEEK_SET);
fprintf(fp, "%s %d %d %.2f\n",
stu->name,
stu->age,
stu->class,
stu->height
);
fclose(fp);
return ;
}
当然这种方法有一个bug:如果文件中的学生数据是固定长度的,那么直接覆盖是可行的。如果记录是可变长度的,这种方法可能会导致文件内容出错。
#define output_format = "%10s%4d%4d%7.2f"
实现等宽输入。
void modify_data_to_file(Student *stu) {
FILE *fp = fopen(file_name, "r+");
fseek(fp, stu->offset, SEEK_SET);
fprintf(fp, output_format,
stu->name,
stu->age,
stu->class,
stu->height
);
fclose(fp);
return ;
}
long output_arr_file(Student *arr, int n) {
FILE *fp = fopen(file_name, "a");
fseek(fp, 0, SEEK_END);
long offset = ftell(fp);
for (int i = 0; i < n; i++) {
fprintf(fp, output_format "\n",
arr[i].name, arr[i].age,
arr[i].class, arr[i].height
);
fclose(fp);
return offset;
}
优化3:实现数据的二进制存储
为什么要用二进制存储?
- 存储效率高
二进制存储直接以机器码的形式存储数据,而不是将数据转换为可读的文本形式。这意味着同样的数据占用的存储空间更少。例如,整数在二进制形式下只需要4个字节,而在文本形式下可能需要更多的字节来表示。
- 读写速度快
由于二进制存储不需要进行格式转换,读写速度通常比文本存储更快。特别是在处理大量数据时,这种速度优势更加明显。
- 数据精度高
在二进制存储中,数据不会因为转换为文本而丢失精度。对于浮点数和其他精度敏感的数据类型尤其重要。
const char *file_name = "student_data.dat";
更改文件后缀名。
int read_from_file(Student *arr) {
int i = 0;
FILE *fp = fopen(file_name, "rb");
if (fp == NULL) return i;
while (1) {
long offset = ftell(fp);
if (fread(&arr[i], sizeof(Student), 1, fp) == 0) break;
arr[i].offset = offset;
i += 1;
}
fclose(fp);
return i;
}
fread的返回值代表了成功读入几个字节。
二进制文件不必吞掉换行符。
long output_arr_file(Student *arr, int n) {
FILE *fp = fopen(file_name, "ab");
fseek(fp, 0, SEEK_END);
long offset = ftell(fp);
fwrite(arr, sizeof(Student), n ,fp);
fclose(fp);
return offset;
}
void modify_data_to_file(Student *stu) {
FILE *fp = fopen(file_name, "rb+");
fseek(fp, stu->offset, SEEK_SET);
fwrite(stu, sizeof(Student), 1, fp);
fclose(fp);
return ;
}
文件打开方式改为操作二进制文件的,把fprintf改为fwrite,把fscanf改为fread。
小结
恭喜你!完成了学生信息管理系统的优化,向着C语言的殿堂更近了一步。
标签:fp,arr,name,学生,stu,file,offset,信息管理系统,优化 From: https://blog.csdn.net/2301_80927620/article/details/139221410