实现对C语言类学生管理系统文件存储的两种方法
学习javascript的时候曾经想做一个留言板的应用,但是却由于不知道如何存储失败了,由于做这个留言板的思路类似于C语言的学生管理系统,故此这次经历让我重新审视自己去学懂C语言的文件操作。
我重新用C语言写了一个留言板系统,当时学习C的时候被学生管理的课设折腾的死去活来,但后来才知道,其实不管是飞机订票还是留言还是学生管理,这类课程设计本质上都要求你用语言去实现一个CRUD完备的应用系统,设计这种系统实际上是一种基本功,而这种系统如果再加上网络通信以及存储(文件或者数据库),就可以称为真正的软件。
废话不多说,代码结构如下:
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "function.h"
#include "LinkList.h"
int main()
{
LNode L;
InitList(&L);
loadFromFile(&L);
while (1)
{
switch (menu())
{
case 1:
add(&L); //1.添加评论功能
break;
case 2:
view(&L); //2.浏览所有评论
break;
case 3:
del(&L); //3.删除评论
break;
case 4:
modify(&L); //4.修改评论
break;
case 5:
exit(0);
default:
fflush(stdin);
printf("请勿暴力测试!\n");
break;
}
}
}
function.h 负责应用的功能实现,要用到链表
#ifndef FUNCTION_H_
#define FUNCTION_H_
#include"LinkList.h"
int menu(); //荣单,每次功能使用结束后翻会调用讯视频 图片
void loadFromFile(LNode *L); //从文件加载数据
void saveToFile(LNode *L); //保存数据到文件中
void s_gets(char *srcText); //处理输入的字符串
void printOne(LNode *p) ;//用于单条评论的打印
void add(LNode *L); //添加评论功能
void view(LNode *L); //浏览所有评论
void del(LNode *L) ; //删除评论
void modify(LNode *L); //修改评论
#endif
LinkList.h 链表的设计
#ifndef LINKLIST_H_
#define LINKLIST_H_
#define MAX_SIZE 100
struct comment //学生的数据,目前只有评论,可再扩展出学号,用户名,日期等信息
{
char text[MAX_SIZE];
};
typedef struct LNode //学生的数据用链表存储
{
struct comment comment;
int id; //节点序号
struct LNode *prev;
struct LNode *next;
} LNode;
LNode *head;
LNode *tail;
void InitList(LNode *L); //初始化链表
LNode *InsList_FromTail(LNode *L); //尾插法添加链表
LNode *InsList_FromHead(LNode *L); //头插法添加链表
int ListLength(LNode head); //测量表长度
void DelList(int index, LNode *L); //按序号删除链表
#endif
C语言对文件通信提供了四种函数:fprintf, fscanf, fread,fwrite。
方法一:fprintf和fscanf
起初的思路是使用fprintf和fscanf,每次添加评论时用一个整形变量datalen统计数据长度并将数据写入文件,最后将datalen写入另一个文件中存储起来;
而读取文件时,按照datalen的大小重新构造链表,并将文件的相关数据重新填入链表。
但这样有很多不便性,对于存储了评论的文件, 要十分关注文件指针在文件中的位置,换行符等特殊字符对文件读取的影响(\n在文件中被fp视为2个字符),在用链表存取的成员值只有一两个时还好,要是评论还有用户名,发言时间,地点等更多的属性时,不仅每次都要修改代码,由于数据是以文本显示,届时用文件指针读入数据,乃至稳定显示数据将是一个复杂的挑战,
但我还是动手实践了一下,借此经历我复习了这两个函数的用法以及文件指针如何操作:
(此外还把《C Primer Plus》中统计行数,词数的程序重新实践了一遍)
伪代码表示为:
loadFromFile函数:
打开数据文件data.txt和记录数据长度的文件len.txt
如果打不开,程序返回错误,反之程序继续执行
从len.txt获取文件长度datalen
for(i->datalen),
每次用头插法构建一个新节点表,就从data.txt获取一个节点的评论数据
将数据导入到新建的节点中
导入完毕,关闭文件
saveFromFile 函数:
检查是否能打开数据文件data.txt
向里面覆写链表中的所有数据,同时记录数据长度
覆写完毕后关闭文件,数据保存到磁盘文件内
void loadFromFile(LNode *L) //从文件加载数据
{
LNode *p;
FILE *fp,*num;
int i, datalen;
char line[MAX_SIZE];
if ((fp = fopen(filePath, "r")) == NULL)
{
fprintf(stdout, "Can't Open data file,Read failed\n");
exit(EXIT_FAILURE);
}
else if ((num = fopen(lenPath, "r")) == NULL)
{
fprintf(stdout, "Can't Open data file,Read failed\n");
exit(EXIT_FAILURE);
}
else
{
fprintf(stdout, "数据文件读取成功!\n");
}
fscanf(num, "%d", &datalen); //从文件开头获取数据长度
printf("当前评论数: %d条\n", datalen);
for (i = 0; i < datalen; i++) //根据数据长度重建链表并赋值
{
p = InsList_FromTail(L); //id,前后指针是链表的固有属性,但comment不是,所以需要赋值
fgets(line,MAX_SIZE,fp);
strcpy(p->comment.text, line);
}
fclose(num);
fclose(fp);
}
void saveToFile(LNode *L) //保存数据到文件中
{
FILE *fp = NULL,*num = NULL;
LNode *p;
int datalen = 0; //统计数据量
if ((fp = fopen(filePath, "w+")) == NULL)
{
fprintf(stdout, "Can't Open data file,Read failed\n");
exit(EXIT_FAILURE);
}
else if ((num = fopen(lenPath, "w+")) == NULL)
{
fprintf(stdout, "Can't Open data file,Read failed\n");
exit(EXIT_FAILURE);
}
for (p = L->next; p != NULL; p = p->next)
{
fprintf(fp, "%s", p->comment.text); //将每个节点的所有数据依次写入文件
datalen++;
}
fprintf(num, "%d", datalen); //用另一个文件标记数据的长度
fclose(num);
fclose(fp);
}
方法二 :fread和fwrite
基本步骤和思路一类似,只不过操作对象从文本变为了二进制数据,得以直接规定用每个节点大小的数据块去写入文件以及赋值给结构体comment,而且由于可用feof判断临界条件,故也不需要用datalen判断数据量了,且comment的结构体成员变动时也不需改代码,整体上方便了许多;
代码如下:
void loadFromFile(LNode *L) //从文件加载数据
{
LNode *p, *newNode;
FILE *fp;
int order = 1;
char line[MAX_SIZE];
p = L;
if ((fp = fopen(filePath, "rb")) == NULL)
{
fprintf(stdout, "Can't Open data file,Read failed\n");
exit(EXIT_FAILURE);
}
else
{
fprintf(stdout, "数据文件读取成功!\n");
}
while (1)
{
newNode = (LNode *)malloc(sizeof(LNode));
fread(&newNode->comment, sizeof(comment), 1, fp);
if (feof(fp))
break;
p->next = newNode;
newNode->prev = p;
newNode->id = order++;
newNode->next = NULL;
p = newNode;
}
fclose(fp);
}
void saveToFile(LNode *L) //保存数据到文件中
{
FILE *fp;
LNode *p;
if ((fp = fopen(filePath, "w+b")) == NULL)
{
fprintf(stdout, "Can't Open data file,Read failed\n");
exit(EXIT_FAILURE);
}
else
{
fprintf(stdout, "数据文件读取成功!\n");
}
if (L->next != NULL)
{
for (p = L->next; p != NULL; p = p->next)
{
fwrite(&p->comment, sizeof(comment), 1, fp); //依次把各结点数据"倒入"文件
}
}
else //如果链表都被删光了
{
fclose(fp);
return 0;
}
fclose(fp);
}
到此讲述了文件存储的两个方法,其实都算是同一种思路,就是如果链表中内容出现了变动,就打开文件将变动的链表内容覆写进去;
而加载文件时,根据链表的长度(或者文件是否eof)来判断为存储数据新建多少个链表节点。
但是这种存储方式还是有一些不足,不如说是C文件存储本身的设计缺陷,
如果要添加新的内容其实不需要覆写文件,直接将文件指针fseek到最后把新增的内容写入就行,
但如果要指定删除某一节点的内容的话,要么将修改后的内容覆写到文件,要么就将要删除内容前后的部分分别写到两个额外文件中重拼起来,然后将原文件删掉,将重拼的文件命名为原文件名,
但是两种方法都必须破坏原文件(一个内容上,一个直接删除)。

我设想直接把文件的指定内容删除就行,被删除内容后端的内容会自动上升与前端的内容结合,上面操作全都只在数据文件中执行,不需要额外的文件,但似乎没有如此的方法。

如果这不是一个1kb文件,而是几十GB乃至更大的大小,执行上述操作(覆写,复制)所浪费的程序性能可想而知,兴许随着的之后的学习我会为此采用数据库来改进存储。
我的留言板系统源码已发到gitee上,欢迎大家学习:
https://gitee.com/tracker647/my-crudpractice
实现对C语言类学生管理系统文件存储的两种方法的更多相关文章
- 20155212 C语言实现linux下pwd命令的两种方法
20155212 C语言实现linux下pwd命令的两种方法 学习pwd命令 通过man pwd命令查看 pwd [OPTION],一般不加参数 -P显示当前目录的物理路径 -L显示当前目录的连接路径 ...
- R语言中将hello打印10次的两种方法
我们有两种方法来做这件事情: 1.for结构 for循环重复的执行一个语句,直到某个变量的值不再包含在序列seq中为止. 语法: for (var in seq) statement 例如: > ...
- C++类的实例化的两种方法
C++ 类的实例化有两种方法: 直接定义对象: 先定义一个类: class A { public: A(); virtual ~A(); ... ... }; 类实现略. 用的时候: A a; ...
- Js类的静态方法与实例方法区分以及jQuery如何拓展两种方法
上学时C#老师讲到对象有两类方法,静态方法(Static)和实例方法(非Static),当时不理解静态是为何意,只是强记. 后来从事前端工作,一直在对类(即对象,Js中严格来说没有类的定义,虽众所周知 ...
- 基于spring-boot和docker-java实现对docker容器的动态管理和监控[附完整源码下载]
(我是个封面) docker简介 Docker 是一个开源的应用容器引擎,和传统的虚拟机技术相比,Docker 容器性能开销极低,因此也广受开发者喜爱.随着基于docker的开发者越来越多,doc ...
- vue-i18n多语言文件归类的两种方法
1.按语言类型归类 流行的做法是按照语言对文件进行归类,目录结构类似于: --lang ----en ------test.json --------"abc": "ab ...
- Win8.1RTM英文版安装中文语言包的两种方法
Windows 8.1和Windows Server 2012 R2 RTM出来了,下载了个英文版的安装上了,发现远景上有朋友提供中文语言包,以下提供两种语言包的安装方法: 一.使用lpksetup命 ...
- OC动态创建的问题变量数组.有数组,在阵列13要素,第一个数据包阵列,每3元素为一组,分成若干组,这些数据包的统一管理。最后,一个数组.(要动态地创建一个数组).两种方法
<span style="font-size:24px;">//////第一种方法 // NSMutableArray *arr = [NSMutable ...
- 在Action类中获得HttpServletResponse对象的四种方法
在struts1.xAction类的execute方法中,有四个参数,其中两个就是response和request.而在Struts2中,并没有任何参数,因此,就不能简单地从execute方法获得Ht ...
- 华为OJ机试题目:两个大整数相乘(纯C语言实现两个大整数相乘,两种方法实现大数相乘)
题目描述: 输出两个不超过100位的大整数的乘积. 输入: 输入两个大整数,如1234567 123 输出: 输出乘积,如:151851741 样例输入: 1234567 123 样例输出: 1518 ...
随机推荐
- VirtualBox 新建虚拟电脑时没有64-bit选项?
好久没用VirtualBox了,没事下载了个准备看下新版的Ubuntu 16.04 & umake命令. 下载&安装完成,准备新建的时候,发现个问题:没有64-bit的选项? 目测了下 ...
- 扫盲ASM
在进行程序跟踪时,会出现汇编.由于ASM盲,所以添加不少烦恼.有烦恼得想办法解决.对,扫盲ASM. 这里是教材,感觉大白话很好理解(感谢 http://www.ruanyifeng.com/blog/ ...
- sql连接处理
序言 数据存储是一个很重要的话题,小到C里面的struct,到os的一个个数据表,大到一个个数据库软件乃至单纯提供数据存储和访问服务的集群,提供数据的快速访问.持久化维护.崩坏数据的恢复,数据的加密维 ...
- 使用PyMuPDF对pdf文件插入文字时 遇到配置本地的字体文件缺仍然使用默认Helvetica字体问题
背景 昨天收到的新需求,一份文件从其他部门发起,进行一些文字填写后盖章,再到我们部门,我们接收到的是pdf文件,所以需要在pdf文件中进行修改,插入当日日期等文字.但有要求字体必须和原文档字体相同. ...
- CF1573B题解
题意: 对于给定的序列 aA1,aA2,-,aAna_{A1},a_{A2},-,a_{An}aA1,aA2,-,aAn.bB1,bB2,-,bBnb_{B1},b_{B2},-,b_{Bn}b ...
- mysql8.0.12+hibernate5.4.1 的一些配置
目录 整体目录结构 第一步 创建数据库 第二步 创建java项目,导入相应的jar包 第三步 创建数据库对应的java类 第四步 创建hibernate映射文件 第五步 创建hibernate核心配置 ...
- 重载(Overloading)与重写(Override)的区别?
重载(Overloading)与重写(Override)的区别? No. 区别 重载 重写 1 英文单词 Overloading Override 2 发生范围 发生在一个类里面 发生在继承关系中 3 ...
- Nacos源码—8.Nacos升级gRPC分析三
大纲 7.服务端对服务实例进行健康检查 8.服务下线如何注销注册表和客户端等信息 9.事件驱动架构源码分析 7.服务端对服务实例进行健康检查 (1)服务端对服务实例进行健康检查的设计逻辑 (2)服务端 ...
- SgLang代码细读-2.forward过程
SgLang代码细读-2.forward过程 总览 Forward的主要过程围绕着 run_batch->TPModelWorker->ModelRunner->Model-> ...
- openjdk8下载地址(附赠)
openjdk下载地址:https://jdk.java.net/ (文末已经为大家下好了,放在网盘里) 进去后点8,win. 安装好后,使用java -version命令如下: 成功安装openjd ...