實現對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上,歡迎大家學習:

浙公網安備 33010602011771號