实现对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 ...
随机推荐
- S7.Net与西门子PLC通讯——纯新手必看
前言 本文档适合从未接触过PLC的.NET开发程序员入门查看.(其实看完了之后,PLC开发也就那样) PLC通讯入门比较难,需要关注的细节比较多.一边学习一边举一反三多思考,一定要自己创建Demo跟 ...
- DevSecOps的实现与相关开源工具
DevSecOps的实现与相关开源工具 DevSecOps是一种以自动化方式在DevOps流程中集成安全工具的方法.DevSecOps不仅仅是引入新的安全工具,还包括关于使用这些工具的必要知识.这需要 ...
- Spring的三级缓存详解
目录 1.什么是三级缓存 2.三级缓存详解 Bean实例化前 属性赋值/注入前 初始化后 总结 3.怎么解决的循环依赖 4.不用三级缓存不行吗 5.总结 一.什么是三级缓存 就是在Bean生成流程中保 ...
- eolinker内置变量更新导致的脚本变化(适用于所有应用前置、后置变量的场景)
内置变量变化情况内置变量变化情况(相关地址https://help.eolinker.com/#/tutorial/?groupID=c-579&productID=13) 通过下表可以了解内 ...
- springboot加载配置文件的优先级
如果springboot有多个配置文件,则加载顺序为项目根路径下的/config > 项目根路径 > resources/config > resources目录下 图示: 如果两个 ...
- 【HUST】网络攻防实践|6_物联网设备固件安全实验|flag2~5速通指南
写在最前:最近没空写报告,实验原理虽然已经摸清了但是没空写.flag2到4是一些大胆的想法的通关方式,flag5是正经通关的.之后写报告的时候会补发正经的实验原理,和flag2到4正常的通关方式. 记 ...
- 『Plotly实战指南』--Plotly与Pandas的深度融合
在数据分析的世界中,数据处理与可视化是密不可分的两个环节. Pandas作为Python数据处理的核心工具,以其强大的数据清洗.转换和分析能力,成为数据科学家和分析师的必备利器: 而Plotly则是交 ...
- Python基础 - 文件处理(上)
读写文件, 文件备份, 上传资料这些操作应该是大家日常工作中每天都要做的事情. 而文件呢, 又有不同的类型(后缀名), 比如 .txt, .xls, .xlsx, .csv, .json, .sql ...
- 【译】.NET Aspire 和 Azure Functions 集成预览版
您是否曾经为 serverless 技术集成到您现有的 .NET 项目中而挣扎过?Visual Studio 的最新更新已经覆盖了该领域.向 .NET Aspire 与 Azure Functions ...
- C#结构体布局规则
以下两个结构体,虽然字段完全一模一样,但因为Pack方式不同,导致它们实际占用内存大小是不一样的! [StructLayout(LayoutKind.Sequential,Pack =1)]//无填充 ...