C语言-文件操作

目录

前言

一、什么是文件

1.程序文件

2.数据文件

3.文件名

二、文件的打开和关闭

1.文件指针

2.文件的打开和关闭

三、文件的顺序读写

1.顺序读写函数介绍

(1)流

(2)fgetc 与 fputc

(3)fgets 与 fputs

(4)fread 和 fwrite

2.对比一组函数

(1)fscanf 和 fprintf

(2)sscanf 和 sprintf

四、文件的随机读写

1.fseek

2.ftell

3.rewind

五、文本文件与二进制文件

六、文件读取结束的判定

1.feof

1.文本文件读取是否结束

2.二进制文件读取是否结束

七、文件缓冲区

八、C语言实现通讯录(文件操作)

1.contact.h

2.contact.c

3.test.c

总结



前言

        为什么使用文件?我们在使用c语言实现通讯录的时候,输入的信息在退出编译器后也被系统释放了,下次运行通讯录的时候,信息又需要重新输入,这样的通讯录估计没人想使用。那么怎样可以把输入的信息保存起来呢,我们就需要把输入的信息保存在硬盘上,从而达到数据的持久化。如果你对计算机有一些了解,应该能够区分内存和硬盘吧,内存容量较小,但是存取速度很快;硬盘容量很大,但是存取速度较慢


一、什么是文件

        文件在计算机中就是我们电脑硬盘上的文件,但是在程序设计中我们涉及到的文件(从文件功能的角度来分类)一般有两种:程序文件和数据文件

1.程序文件

        包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

        

2.数据文件

        文件的内容不一定是程序,而是程序运行时读写的数据。程序运行时可以从文件中获取数据,或者保存程序输出的数据到文件中

        

本篇博客讨论的是数据文件

3.文件名

        一个文件要有一个唯一的文件标识(就是我们通常所说的文件名),便于用户识别和引用。文件名包含三部分:文件路径+文件名主干+文件后缀,

例如:c:\vs2022\3_31\test.tst


二、文件的打开和关闭

        通常情况下,如果我们想要喝到瓶子里的水,我们需要先打开瓶盖,然后喝水,最后还会拧上瓶盖。文件的打开和关闭其实也与此类似。

1.文件指针

        在学习文件的打开和关闭之前,我们需要先了解一个关键的概念——文件类型指针,简称文件指针。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(例如:文件名,文件状态以及存储路径)。这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名为FILE

例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型声明

struct _iobuf
{
	char* _ptr;
	int _cnt;
	char* _base;
	int _flag;
	int _file;
	int _charbuf;
	int _bufsiz;
	char* _tmpfname;
};

typedef struct _iobuf FILE;

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,
并填充其中的信息,使用者一般可以通过FILE的指针来维护这个结构体的变量

FILE *pf;    //文件指针变量

使用者可以通过文件指针变量访问该文件

        

2.文件的打开和关闭

        文件在读写之前需要先打开文件,在使用完之后需要关闭文件,(就像我们使用动态内存空间需要申请空间和释放空间一样)。在编写程序的时候,打开文件会返回一个FILE类型的指针变量指向该文件,相当于建立了上图所示的关系。

        ANSIC规定使用 fopen 函数来打开文件,fclose 函数来关闭文件

        使用fopen打开文件成功,返回一个指向该文件的FILE类型的文件指针;打开文件失败则会返回一个空指针NULL

在使用fopen函数的时候,我们还需要确定打开的方式:(参数:const char* mode)

        

读写文件代码演示:

        ↑↑↑你是否可以联想到我们学过的malloc和free函数呢?


三、文件的顺序读写

1.顺序读写函数介绍

功能                          函数名             适用于

字符输入函数            fgetc                所有流输入

字符输出函数            fputc                所有流输出

文本行输入函数        fgets                 所有流输入

文本行输出函数        fputs                 所有流输出

格式化输入函数        fsacnf               所有流输入

格式化输出函数        fprintf                所有流输出

二进制输入               fread                 文件

二进制输出               fwerite               文件

        

(1)流

        我们知道电流、水流和气流,它们都是一个抽象的概念,流——形象地描述了电、水和气的运动状态。数据在计算机中的交换也可以用流来形容(数据流),数据之间的交换就需要通过流来实现

        

(2)fgetc 与 fputc

        把26个字母写入文件,然后读取出来

(3)fgets 与 fputs

        

(4)fread 和 fwrite

        

        

2.对比一组函数

scanf\fscanf\sscanf

printf\fprintf\sprintf

scanf——针对标准输入流(stdin)的格式化函数

printf——针对标准输出流(stdout)的格式化函数

fscanf——针对所有输入流(文件流/stdin)的格式化函数

fprintf——针对所有输出流(文件流/stdout)的格式化函数

sscanf——把字符串转换为格式化的数据

sprintf——把格式化的数据转换为字符串

(1)fscanf 和 fprintf

        

(2)sscanf 和 sprintf

        


四、文件的随机读写

1.fseek

根据文件指针的位置和偏移量来定位

参数offset:偏移量;参数int origin:SEEK_SET(文件的起始位置)、SEEK_CUR(文件的当前位置)、SEEK_END(文件的末尾)

2.ftell

返回文件指针相对于起始位置的偏移量

        

3.rewind

让文件指针回到文件的起始位置

        


五、文本文件与二进制文件

        根据数据的组织形式,数据文件可以分为文本文件和二进制文件。数据在内存中以二进制的形式存储,如果不加以转换地输出到外存,就是二进制文件。如果要求在外存上以ASCII码的形式存储,则需要在存储前转换数据,以ASCII字符的形式存储的文件就是文本文件。

        

        


六、文件读取结束的判定

1.feof

        作用:当文件读取结束的时候,判断读取结束的原因是读取失败结束,还是遇到文件末尾结束(注意:在文件读取的过程中,不能直接使用feof函数的返回值来判断文件是否结束)

1.文本文件读取是否结束

        判断返回值是否为EOF(fgetc),或者NULL(fgets)

例如:

    fgetc 判断是否为 EOF

1)遇到文件末尾,返回EOF,同时设置一个状态(表示遇到文件末尾了),可以使用feof来检测这个状态

2)遇到错误,返回EOF,同时设置一个状态(表示遇到错误了),可以使用ferror来检测这个状态

    fgets 判断是否为 NULL

文本文件:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int ch = 0;
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	while ((ch = fgetc(pf)) != EOF)
	{
		putchar(ch);
	}
	//判断文件读取结束的原因
	if (ferror(pf))
	{
		printf("I/O error when reading\n");
	}
	else if(feof(pf))
	{
		printf("End of file reached successfully\n");
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

2.二进制文件读取是否结束

        判断返回值是否小于实际要读的个数

例如:

    fread 判断返回值是否小于实际要读的个数

二进制文件:

#include <stdio.h>
#include <stdlib.h>

#define SIZE 5

int main(void)
{
	double a[SIZE] = { 1.,2.,3.,4.,5. };
	FILE* fp = fopen("test.bin", "wb");//必须用二进制模式
	fwrite(a, sizeof * a, SIZE, fp);//写double的数组
	fclose(fp);
	double b[SIZE];
	fp = fopen("test.bin", "rb");
	size_t ret_code = fread(b, sizeof * b, SIZE, fp);
	if (ret_code == SIZE) 
	{
		printf("读取数组成功:");
		for (int n = 0; n < SIZE; ++n)
			printf("%f ", b[n]);
		printf("\n");
	}
	else 
	{
		if (feof(fp))
			printf("读取文件错误\n");
		else if(ferror(fp)) 
			perror("读取文件错误\n");
	}
	fclose(fp);
}

        


七、文件缓冲区

        ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

        

VS2022win11环境测试

#include <stdio.h>
#include <windows.h>

int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}

因为有缓冲区的存在,C语言在操作文件的时候,需要刷新缓冲区或者在文件操作结束的时候关闭文件,否则会产生读写文件的问题


八、C语言实现通讯录(文件操作)

1.contact.h

#pragma once

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#define MAXSIZE 1000
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 15
#define MAX_ADDR 30
#define DEFAULT_SZ 5
#define INT_SZ 3

typedef struct PeoInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;

//静态版本的设计
//typedef struct Contact
//{
//	PeoInfo data[MAXSIZE];//存放数据
//	int sz;//记录通讯录中的有效信息个数
//}Contact, * pContact;

//动态版本的设计
//默认能够存放5个人的信息
//如果不够,每次增加3个信息的空间
typedef struct Contact
{
	PeoInfo *data;//data指向了存放数据的空间
	int sz;//记录通讯录中的有效信息个数
	int capacity;//记录通讯录当前的容量
}Contact, * pContact;

//初始化通讯录
void InitContact(Contact* pc);

//增加指定联系人
void AddContact(Contact* pc);

//显示联系人信息
void ShowContact(const Contact* pc);

//删除指定联系人
void DelContact(pContact pc);

//查找指定联系人
void SearchContact(const Contact* pc);

//修改通讯录
void ModifyContact(Contact* pc);

//排序通讯录元素
void SortContact(Contact* pc);

//清空所有联系人
void DestroyContact(Contact* pc);

//保存数据到文件
void SaveContact(Contact* pc);

2.contact.c

#include "contact.h"

//静态版本的设计
//void InitContact(Contact* pc)
//{
//	pc->sz = 0;
//	memset(pc->data, 0, sizeof(pc->data));
//}

//动态版本的设计
void InitContact(Contact* pc)
{
	pc->sz = 0;
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		printf("通讯录初始化失败:%s\n", strerror(errno));
		return;
	}
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;
}

//静态版本的设计
//void AddContact(Contact* pc)
//{
//	if (pc->sz == MAXSIZE)
//	{
//		printf("通讯录已满,无法增加\n");
//		return;
//	}
//	printf("请输入名字:>");
//	scanf("%s", pc->data[pc->sz].name);
//	printf("请输入年龄:>");
//	scanf("%d", &(pc->data[pc->sz].age));
//	printf("请输入性别:>");
//	scanf("%s", pc->data[pc->sz].sex);
//	printf("请输入电话:>");
//	scanf("%s", pc->data[pc->sz].tele);
//	printf("请输入地址:>");
//	scanf("%s", pc->data[pc->sz].addr);
//
//	pc->sz++;
//	printf("添加成功\n");
//}

//扩容失败返回0;扩容成功或者不需要扩容返回1
int CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INT_SZ) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("CheckCapacity:%s\n", strerror(errno));
			return 0;
		}
		pc->data = ptr;
		pc->capacity += INT_SZ;
		printf("增容成功,当前容量:%d\n", pc->capacity);
		return 1;
	}
	return 1;
}

//动态版本的设计
void AddContact(Contact* pc)
{
	if (0 == CheckCapacity(pc))
	{
		printf("空间不够,扩容失败\n");
		return;
	}
	else
	{
		printf("请输入名字:>");
		scanf("%s", pc->data[pc->sz].name);
		printf("请输入年龄:>");
		scanf("%d", &(pc->data[pc->sz].age));
		printf("请输入性别:>");
		scanf("%s", pc->data[pc->sz].sex);
		printf("请输入电话:>");
		scanf("%s", pc->data[pc->sz].tele);
		printf("请输入地址:>");
		scanf("%s", pc->data[pc->sz].addr);

		pc->sz++;
		printf("添加成功\n");
	}
}

void ShowContact(const Contact* pc)
{
	int i = 0;
	//姓名      年龄      性别     电话      地址
	//zhangsan 20        男      123456    北京
	//
	//打印标题
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	//打印数据
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s %-4d %-5s %-12s %-30s\n",
			pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

static int FindByName(const Contact* pc, char name[])
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
		{
			return i;
		}
	}
	return -1;
}

void DelContact(pContact pc)
{
	char name[MAX_NAME] = { 0 };
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	//删除
	//1. 找到要删除的人 - 位置(下标)
	printf("输入要删除人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	int i = 0;
	//2. 删除 - 删除pos位置上的数据
	for (i = pos; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功\n");
}

void SearchContact(const Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	//查找
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	//打印
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	//打印数据
	printf("%-10s %-4d %-5s %-12s %-30s\n",
		pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}

void ModifyContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	//修改
	printf("请输入名字:>");
	scanf("%s", pc->data[pos].name);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pos].age));
	printf("请输入性别:>");
	scanf("%s", pc->data[pos].sex);
	printf("请输入电话:>");
	scanf("%s", pc->data[pos].tele);
	printf("请输入地址:>");
	scanf("%s", pc->data[pos].addr);

	printf("修改成功\n");
}

//按照名字来排序
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

void SortContact(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_name);
	printf("排序成功\n");
}

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
	printf("释放内存\n");
}

void SaveContact(Contact* pc)
{
	FILE* pf = fopen("contact.dat", "wb");
	if (pf == NULL)
	{
		perror("SaveContact::fopen");
		return;
	}
	//写入数据
	for (int i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	printf("保存成功...\n");
}

3.test.c

#include "contact.h"

void menu()
{
	printf("|********************************|\n");
	printf("|****   0. exit    1. add    ****|\n");
	printf("|****   2. del     3. search ****|\n");
	printf("|****   4. modify  5. show   ****|\n");
	printf("|****   6. sort    7. clean  ****|\n");
	printf("|********************************|\n");
}

enum Option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
	CLEAN
};

int main()
{
	int input = 0;
	//构造一个通讯录
	Contact con;
	//初始化通讯录
	InitContact(&con);
	do
	{
		menu();
		printf("请选择 :>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			SaveContact(&con);
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		case CLEAN:
			DestroyContact(&con);
			//ShowContact(pc);
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

4.运行代码截图以及二进制文件contact.dat

 


总结

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

φ冰霰ξ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值