文件操作
函数名:rewind()
函数原型:void rewind(FILE *stream);
函数功能:光标移动到文件开头
函数参数:FILE *stream:fp
函数返回值:无
函数使用: rewind(fp) == fseek(fp,0,0);
函数名:ftell()
函数原型:long ftell(FILE *stream);
函数功能:计算从文件开头到当前光标位置的字节数
函数参数:FILE *stream:fp
函数返回值:返回文件开头到当前光标位置的字节数
函数使用: 可用于计算文件大小操作: fseek(fp,0,2); long n = ftell(fp);
函数名:remove()
函数原型:int remove(const char *pathname);
函数功能:删除文件
函数参数:const char *pathname:文件路径---字符串形式
函数返回值:成功返回 0,失败返回-1
函数使用: remove(“../1.txt”);
函数名:feof()
函数原型:int feof(FILE *stream);
函数功能:判断光标是否在文件末尾
函数参数:FILE *stream:fp
函数返回值:到文件末尾,返回非 0,没有到文件末尾,返回 0
函数使用:
rewind(fp);
while(feof(fp)==0) {
Node *new = Create_Node();
fscanf(“%s %d\n”,new->inf.name,&new->inf.id);
//头插法或尾插法加入到链表中
}
函数名:fgets()
函数原型:char *fgets(char *s, int size, FILE *stream);
函数功能:从文件流读取指定长度的字符串
函数参数:
- char *s:数据在程序中保存的位置
- int size:字符串总长度,要比想要读取的有效字符个数多 1 想要读 5 个有效字符,需要填 6
- FILE *stream:fp
函数返回值:返回读取到的字符串的所在地址
函数使用:
char a[100]={0};
fgets(a,100,fp);
//实际读取 99 个有效字符,还有一个 ‘\0’
函数名:fputs()
函数原型:int fputs(const char *s, FILE *stream);
函数功能:向指定文件写入字符串
函数参数:
- const char *s:想要写入的字符串
- FILE *stream:fp
函数返回值:成功返回非负数,失败返回 EOF
函数使用: fputs(“hello”,fp);
函数名:fgetc()
函数原型:int fgetc(FILE *stream);
函数功能:从指定文件读取一个字符
函数参数:FILE *stream:fp
函数返回值:返回读取到的字符
函数使用: char a=fgetc(fp);
函数名:fputc()
函数原型:int fputc(int c, FILE *stream);
函数功能:写入一个字符到指定文件
函数参数:int c:想要写入的字符 FILE *stream:fp
函数返回值:成功返回写入的字符,失败返回 EOF
函数使用: fputc(‘a’,fp);
函数名:fwrite()
函数原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
函数功能:ptr 起始的数据块,一块 size 大小,共写入 nmemb 个,到文件 stream
函数参数:
- const void *ptr:需要写入数据块的起始地址
- size_t size:一个数据块的大小
- size_t nmemb:一共几个数据块
- FILE *stream:fp
函数返回值:返回写入的数据块的个数
函数使用: 块读块写---数据带数据类型
int a[5]; //1*20 20*1 5*4
fwrite(a,4,5,fp);
函数名:fread()
函数原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
函数功能:fread
函数从文件流 stream
中读取 nmemb
个元素,每个元素的大小为 size
字节,并将这些数据存储到由 ptr
指向的内存区域中。
函数参数:
- void *ptr:读取的数据块保存位置的起始地址
- size_t size:一个数据块的大小
- size_t nmemb:一共几个数据块
- FILE *stream:fp
函数返回值:返回读取的数据块的个数
函数使用:
int a[5]={0};
fread(a,20,1,fp);
fread
函数从文件流fp
中读取数据,并将其存储到数组a
中。- 参数解释:
a
:指向存储读取数据的内存区域的指针。20
:每个元素的大小(以字节为单位)。在这里,20
表示每次读取 20 个字节。1
:要读取的元素个数。在这里,1
表示读取 1 个 20 字节的块。fp
:指向文件流的指针。
示例代码
#include <stdio.h>
int main()
{
// 尝试以读写方式打开名为"2.txt"的文件,如果文件不存在则创建
FILE * fp = fopen("2.txt","w+");
int a[5]={1,2,3,4,5};
// 以格式化输出的方式将数组 a 的元素写入文件(此部分被注释)
// fprintf(fp,"%d%d%d%d%d",a[0],a[1],a[2],a[3],a[4]);
// 以二进制形式将数组 a 的内容写入文件,每个元素大小为 4 字节,共写入 5 个元素
fwrite(a,4,5,fp);
int b[5]={0};
// 将文件指针重新定位到文件开头
rewind(fp);
// 从文件中读取数据到数组 b 中,每次读取 20 字节,读取 1 次
fread(b,20,1,fp);
// 打印数组 b 的元素
printf("%d%d%d%d%d\n",b[0],b[1],b[2],b[3],b[4]);
// 关闭文件
fclose(fp);
return 0;
}
这段代码主要实现了向文件写入数据和从文件读取数据的功能。首先,它打开一个文件并将整型数组 a 的内容以二进制形式写入该文件。 然后,重新定位文件指针到开头,从文件读取数据到另一个整型数组 b ,最后打印数组 b 的元素并关闭文件。
项目练习
这里继续以学生信息管理系统为练习。
我这里实现了新增信息、展示信息、修改信息、查询信息、删除信息、插入信息、排序,共7个功能。将其做模块化应用。
头文件stu.h
#ifndef _STU_H_
#define _STU_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct info{
int id;
char name[20];
float score;
};
typedef struct student{
struct info mesg;
struct student *next;
}M,*list;
extern list head;
char menu(void);
list Create(void);
void Add(void);
void Load(void);
void Save(list head);
void Print(void);
list Search(void);
void Change(void);
void Delete(void);
void Insert(void);
#endif
我的头文件里包含了所有函数的声明。同时,将信息封装为结构体,嵌套到链表中。并且,将头指针外部声明。
主函数main.c
#include "stu.h"
list head=NULL;
int main() {
Load();//调用数据加载
char op = 0;
while (1) {
op = menu();
switch (op) {
case '0': // 0. 退出
return 0;
case '1': // 1. 新增信息
Add();
break;
case '2': // 2. 展示信息
Print();
break;
case '3':
Search();//查询信息
break;
case '4': // 4. 修改信息
Change();
break;
case '5': // 5. 删除信息
Delete();
break;
case '6': // 6. 插入信息
Insert();
break;
case '7': // 7. 排序
Sequence();
break;
default:
printf("Error!\n");
}
printf("Enter to continue...\n");
while(getchar()!='\n');//清空缓冲区
system("clear");//清屏
}
return 0;
}
char menu(void) {//菜单函数
char val = 0; // 定义选择项
printf("What do you want to do?\n");
printf("0. 退出\t1. 新增信息\t2. 展示信息\t3. 查询信息\n");
printf("4. 修改信息\t5. 删除信息\n");
printf("6. 插入信息\t7. 排序\n");
scanf("%c", &val);
while (getchar() != '\n');
return val;
}
这里我做了switch菜单用于管理系统功能。并将菜单选项封装为函数。
在主函数里先调用文件数据加载函数,并在选择功能后做了清空缓存区的处理。
Add.c
#include "stu.h"
void Add(void){
list newN=Create();
if(newN==NULL){
printf("error\n");
return ;
}
int id;
while (1) {
printf("请输入id信息\n");
scanf("%d", &id);
while (getchar() != '\n');
// 检查 id 是否已存在
list cur = head;
int exists = 0;
while (cur != NULL) {
if (cur->mesg.id == id) {
exists = 1;
break;
}
cur = cur->next;
}
if (!exists) {
break; // 如果 id 唯一,跳出循环
} else {
printf("该 id 已存在,请输入新的 id。\n");
}
}
newN->mesg.id = id;
printf("请输入name信息\n");
scanf("%s",newN->mesg.name);
while(getchar()!='\n');
printf("请输入score信息\n");
scanf("%f",&newN->mesg.score);
while(getchar()!='\n');
newN->next=head;
head=newN;
Save(head);//调用保存函数写入文件
}
void Save(list head){
FILE *fp = fopen("message.txt", "w"); //以写形式打开文件
if (fp == NULL) { // 检查文件是否成功打开
perror("fopen"); // 如果打开失败,输出错误信息
return ; // 返回
}
list cur=head;
while(cur!=NULL){//cur遍历链表,将全部信息重新写入覆盖数据
fprintf(fp,"%d %s %f\n",cur->mesg.id,cur->mesg.name,cur->mesg.score);
cur=cur->next;
}
fclose(fp);
}
void Load(void){
FILE *fp = fopen("message.txt", "r"); //以读形式打开文件
if(fp==NULL){
return;
}
list newN;
while (feof(fp)==0) {
newN = Create();
if (newN == NULL) {
perror("malloc");
fclose(fp);
return;
}
if (fscanf(fp, "%d %s %f\n", &newN->mesg.id, newN->mesg.name, &newN->mesg.score) == EOF) //??
{
free(newN); // 如果读取失败,释放新分配的内存
break;
}
newN->next = head;
head = newN;
}
fclose(fp);
}
在这个源文件里,我定义了三个函数:新增数据、数据写入、数据加载
在新增数据函数Add()中,
这个函数用于向链表中添加新的节点。
首先创建一个新的链表节点 newN
,如果内存分配失败(newN
为 NULL
),打印错误信息并返回。
然后进入一个无限循环,提示用户输入 id
信息,并使用一个内层循环检查输入的 id
是否已经在链表中存在。如果不存在,就跳出无限循环;如果存在,提示用户重新输入。
接着,将用户输入的合法 id
赋值给新节点的 id
成员。
之后,提示用户输入 name
和 score
信息,并将其分别赋值给新节点的相应成员。
然后,将新节点插入到链表头部(通过让新节点的 next
指针指向原来的头节点,然后更新头节点为新节点)。
最后,调用 Save
函数将更新后的链表数据写入文件。
在数据写入函数Save()中,
这个函数的作用是将一个链表中的数据保存到一个名为 message.txt
的文件中。首先,函数以只写模式打开文件,如果文件打开失败(fp
为 NULL
),会输出错误信息并返回。
然后,定义一个链表节点指针 cur
并初始化为链表的头节点 head
。
通过一个 while
循环,只要 cur
不为 NULL
(即未到达链表末尾),就使用 fprintf
函数将当前节点的数据(包括 id
、name
和 score
)按照指定的格式写入文件中,格式为 %d %s %f\n
,分别对应整数、字符串和浮点数,并在每个数据项后添加换行符。
每次写入后,将 cur
指针移动到下一个节点。
最后,关闭文件,完成数据的保存操作。
在数据加载函数Load()中,
主要功能是从一个名为 message.txt
的文件中读取数据,并将其构建为一个链表。
函数首先以只读模式打开文件,如果文件打开失败(fp
为 NULL
),则直接返回。
然后创建一个新的链表节点 newN
。
在 while
循环中,只要还未到达文件末尾(通过 feof(fp)==0
判断),就继续执行以下操作:
尝试为新节点分配内存,如果内存分配失败(newN
为 NULL
),打印错误信息并关闭文件后返回。
使用 fscanf
函数从文件中读取数据到新节点的成员中。如果读取失败(返回值为 EOF
),释放新分配的内存并退出循环。
如果读取成功,将新节点插入到链表头部(通过让新节点的 next
指针指向原来的头节点,然后更新头节点为新节点)。
最后,关闭文件。
其中的 if (fscanf(fp, "%d %s %f\n", &newN->mesg.id, newN->mesg.name, &newN->mesg.score) == EOF)
这部分,它的作用是检查从文件中读取数据的操作是否成功。如果读取不成功(即达到了文件末尾或读取过程中出现错误),返回值为 EOF
,就会执行后面的释放内存和退出循环的操作。
create.c
#include "stu.h"
list Create(void){
list newN=(list)malloc(sizeof(M));
if(newN==NULL){
perror("malloc");
return NULL;
}
memset(newN,0,sizeof(M));
return newN;
}
这个函数用于创建一个新的链表节点。
首先,使用 malloc
函数分配一个大小为 sizeof(M)
的内存空间,并将返回的指针强制转换为 list
类型,然后赋值给 newN
。如果内存分配失败(newN
为 NULL
),打印错误信息并返回 NULL
。
接着,使用 memset
函数将新分配的内存空间初始化为 0 。
最后,返回新创建的节点指针。
Print.c
#include "stu.h"
void Print(void){
if (head == NULL) {
printf("No students in the list.\n");
return;
}
list i=head;
printf("%-8s%-8s%-8s\n","id","name","score");
while(i!=NULL){
printf("%-4d\t%-8s%-8.2f\n",i->mesg.id,i->mesg.name,i->mesg.score);
i=i->next;
}
printf("\n");
}
这个函数用于遍历打印输出链表中的数据
Search.c
#include "stu.h"
list Search(void){
list cur=head;
int flag=0,tmp=0;
printf("请输入需处理的学生id:");
scanf("%d",&tmp);
while(getchar()!='\n');
while(cur!=NULL){
if(tmp==cur->mesg.id){
printf("%-8s%-8s%-8s\n","id","name","score");
printf("%-4d\t%-8s%-8.2f\n",cur->mesg.id,cur->mesg.name,cur->mesg.score);
flag++;
return cur;
}
cur=cur->next;
}
if(flag==0){
printf("这儿没有这个学生\n");
return NULL;
}
}
这函数用来通过学号id查询学生信息,并返回查询到的信息节点指针
Change.c
#include "stu.h"
void Change(void){
list cur=Search();
if (cur == NULL) {
printf("未找到该学生信息,无法修改。\n");
return;
}
printf("修改成绩为:");
scanf("%f",&cur->mesg.score);
Save(head);//调用保存函数写入文件
}
这个函数用来更改学生成绩数据,最后更新到文件中。
Delete.c
#include "stu.h"
void Delete(void){
list cur=Search();
if (cur == NULL) {
printf("未找到该学生信息,无法修改。\n");
return;
}
if(cur==head){
head=cur->next;
}else{
list i=head;
while(i->next==cur){
i->next=cur->next;
}
free(cur);
Save(head);//调用保存函数写入文件
}
}
这个函数用来删除学生信息
Insert.c
#include "stu.h"
void Insert(void){
int sec=0;
printf("要插入在0.第1个么?1.或者谁之后?\n");
scanf("%d",&sec);
if(sec==0){
Add();
return;
}else{
list cur=Search();
list ins=Create();
int id;
while (1) {
printf("请输入id信息\n");
scanf("%d", &id);
while (getchar() != '\n');
// 检查 id 是否已存在
list cur1 = head;
int exists = 0;
while (cur1 != NULL) {
if (cur1->mesg.id == id) {
exists = 1;
break;
}
cur1 = cur1->next;
}
if (!exists) {
break; // 如果 id 唯一,跳出循环
} else {
printf("该 id 已存在,请输入新的 id。\n");
}
}
ins->mesg.id = id;
printf("请输入name信息\n");
scanf("%s",ins->mesg.name);
while(getchar()!='\n');
printf("请输入score信息\n");
scanf("%f",&ins->mesg.score);
while(getchar()!='\n');
ins->next=cur->next;
cur->next=ins;
Save(head);//调用保存函数写入文件
}
}
这个函数用于插入信息。首先判断插入位置,再分情况处理。
Sequence.c
#include "stu.h"
void Sequence(void){
int op=0;
printf("请选择排序规则:0.按学号排序\t1.按成绩排序\n");
scanf("%d",&op);
if(op){//按成绩
list cur=head;
list later=head;
for(cur=head;cur->next!=NULL;cur=cur->next)
{
for(later=head;later->next!=NULL;later=later->next){
if(later->mesg.score<later->next->mesg.score){
struct info tmp =later->next->mesg;
later->next->mesg=later->mesg;
later->mesg=tmp;
}
}
}
}else{//按学号
list cur=head;
list later=head;
for(cur=head;cur->next!=NULL;cur=cur->next)
{
for(later=head;later->next!=NULL;later=later->next){
if(later->mesg.id>later->next->mesg.id){
struct info tmp =later->next->mesg;
later->next->mesg=later->mesg;
later->mesg=tmp;
}
}
}
}
Save(head);
}
最后排序,整理信息。