【C语言文件操作】

81 篇文章 2 订阅
10 篇文章 0 订阅

大家好,这里是我时隔许久又一良心巨著,主要是介绍了文件的相关操作,特意整理出来一篇博客供我们一起复习和学习,如果文章中有理解不当的地方,还希望朋友们在评论区指出,我们相互学习,共同进步!

一:为什么要使用文件

在这里插入图片描述

我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。
这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

二:什么是文件

磁盘上的文件是磁盘文件,但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

2.1:程序文件

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

2.2:数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

三:文本文件和二进制文件

3.1:文本文件

  • 基于字符编码,常见编码有ASCII、UNICODE等。
  • 一般可以使用文本编辑器直接打开。
  • 数5678的以ASCII存储形式(ASCII码)为:00110101 00110110 00110111 00111000

3.2:二进制文件

  • 基于值编码,自己根据具体应用,指定某个值是什么意思。
  • 把内存中的数据按其在内存中的存储形式原样输出到磁盘上。
  • 数5678的存储形式(二进制码)为:00010110 00101110

四:文件的打开和关闭

4.1:文件指针

在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。

typedef struct
{
	short           level;	//缓冲区"满"或者"空"的程度 
	unsigned        flags;	//文件状态标志 
	char            fd;		//文件描述符
	unsigned char   hold;	//如无缓冲区不读取字符
	short           bsize;	//缓冲区的大小
	unsigned char   *buffer;//数据缓冲区的位置 
	unsigned        ar;	 //指针,当前的指向 
	unsigned        istemp;	//临时文件,指示器
	short           token;	//用于有效性的检查 
}FILE;

FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。

声明FILE结构体类型的信息包含在头文件“stdio.h”中,一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各种操作。
在这里插入图片描述

4.2:文件的打开

FILE * fopen(const char * filename, const char * mode);
功能:打开文件
参数:

filename:需要打开的文件名,根据需要加上路径
mode:打开文件的模式设置

返回值:

成功:文件指针
失败:NULL

int main()
{
	FILE *fp = NULL;

	// "\\"这样的路径形式,只能在windows使用
	// "/"这样的路径形式,windows和linux平台下都可用,建议使用这种
	// 路径可以是相对路径,也可是绝对路径
	fp = fopen("../test", "w");
	//fp = fopen("..\\test", "w");

	if (fp == NULL) //返回空,说明打开失败
	{
		//perror()是标准出错打印函数,能打印调用库函数出错原因
		perror("open");
		return -1;
	}

	return 0;
}

4.2:文件的关闭

任何文件在使用后应该关闭:

  • 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存。
  • 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen打开文件会失败。
  • 如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。
	int main()
{
	//printf("hehe\n");
	//打开文件
	FILE* pf = fopen("test5.txt", "r");
	if (pf == NULL)
	{
		//printf("打开文件失败\n");
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读文件

	//关闭文件
	fclose(pf);
	pf = NULL;
	system("pause");
	return 0;
}

五:文件的顺序读写

5.1:按照字符读写文件fgetc、fputc

一):写文件
int fputc(int ch, FILE * stream);
功能:将ch转换为unsigned char后写入stream指定的文件中
参数:

ch:需要写入文件的字符
stream:文件指针

返回值:

成功:成功写入文件的字符
失败:返回-1

代码示例:

int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	char buf[] = "this is a test for fputc";
	int i = 0;
	int n = strlen(buf);
	for (i = 0; i < n; i++)
	{
		//往文件fp写入字符buf[i]
		int ch = fputc(buf[i], pf);
		printf("ch = %c\n", ch);
	}

	fclose(pf);
	pf = NULL;

	return 0;
}

结果展示:
在这里插入图片描述
二):文件结尾

在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。

#define EOF     (-1)

三):读文件
int fgetc(FILE * stream);
功能:从stream指定的文件中读取一个字符
参数:

stream:文件指针

返回值:

成功:返回读取到的字符
失败:-1

代码示例

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读文件
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c", ch);
	}

	fclose(pf);
	pf = NULL;

	return 0;
}

this is a test for fputc请按任意键继续. . .

六:按照行读写文件fgets、fputs

6.1:写文件

int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 ‘\0’ 不写入文件(且遇到字符串结束符停止)。
参数:

str:字符串
stream:文件指针

返回值:

成功:0
失败:-1

代码示例:

int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	char *buf[] = { "123456\n", "bbbbbbbbbb\n", "ccccccccccc\n" };
	int i = 0;
	int n = 3;
	for (i = 0; i < n; i++)
	{
		int len = fputs(buf[i], pf);
		printf("len = %d\n", len);
	}
	fclose(pf);
	pf = NULL;

	return 0;
}

len = 0
len = 0
len = 0
请按任意键继续. . .

从结果len的打印知道,写入成功。
结果展示:
在这里插入图片描述

6.2:读文件

char * fgets(char * str, int size, FILE * stream);
功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 ‘\0’ 作为字符串结束。
参数:

str:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针

返回值:

成功:成功读取的字符串
读到文件尾或出错: NULL

代码示例

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	char buf[1000] = {0};
	//读文件
	fgets(buf, 3, pf);
	printf("%s", buf);

	fgets(buf, 3, pf);
	printf("%s", buf);

	printf("\n");
	fclose(pf);
	pf = NULL;

	return 0;
}

1234
请按任意键继续. . .

由一)知道data.txt文件中第一行存的123456,由代码中fgets(buf, 3, pf);第一次读取12,再一次读取34,所以打印出1234。

也就是说虽然叫行读写,但不是每次都读取一行,它是一行读取完再读取下一行,每次读取size-1个字符。如果文件中的字符少于size,那么读取出来时,自动加一个\n。

个人理解:文件中每一行比如这里的123456,第一行与第二行之间是有一个换行符号\n的,所以其实比如上述代码的第一个size填写6,实际读出的是12345,然后下一个读取的是6和\n。打印结果如下:


fgets(buf, 6, pf);
printf("%s", buf);

fgets(buf, 3, pf);
printf("%s", buf);

123456
请按任意键继续. . .

6.3:实现代码拷贝

int main()
{
	//实现一个代码将data.txt 拷贝一份 生成data2.txt
	FILE* pr = fopen("data.txt", "r");
	if (pr == NULL){
		printf("open for reading: %s\n", strerror(errno));
		return 0;
	}
	FILE* pw = fopen("data2.txt", "w");
	if (pw == NULL){
		printf("open for writting: %s\n", strerror(errno));
		fclose(pr);
		pr = NULL;
		return 0;
	}
	//拷贝文件
	int ch = 0;
	while ((ch = fgetc(pr)) != EOF)
	{
		fputc(ch, pw);
	}
	fclose(pr);
	pr = NULL;
	fclose(pw);
	pw = NULL;
	return 0;
}

七:按照格式化文件fprintf、fscanf

7.1:写文件

int fprintf(FILE * stream, const char * format, …);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 ‘\0’ 为止。
参数:

stream:已经打开的文件
format:字符串格式,用法和printf()一样

返回值:

成功:实际写入文件的字符个数
失败:-1

代码示例:

struct Stu
{
	char name[20];
	int age;
	double d;
};
int main()
{
	struct Stu s = { "张三", 20, 95.5 };
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//写格式化的数据
	fprintf(pf, "%s %d %lf", s.name, s.age, s.d);

	fclose(pf);
	pf = NULL;

	return 0;
}

结果展示:
在这里插入图片描述

7.2:读文件

int fscanf(FILE * stream, const char * format, …);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:

stream:已经打开的文件
format:字符串格式,用法和scanf()一样

返回值:

成功:参数数目,成功转换的值的个数
失败: - 1

代码示例:

int main()
{
	struct Stu s = { 0 };
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读格式化的数据
	fscanf(pf, "%s %d %lf", s.name, &(s.age), &(s.d));

	printf("%s %d %lf\n", s.name, s.age, s.d);
	fclose(pf);
	pf = NULL;
	return 0;
}

张三 20 95.500000
请按任意键继续. . .

八:按照块读写文件fread、fwrite

按照块读写主要是针对结构体等自定义类型,以二进制方式写。

8.1 写文件

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:

ptr:准备写入文件数据的地址
size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
stream:已经打开的文件指针

返回值:

成功:实际成功写入文件数据的块数目,此值和 nmemb 相等
失败:0

代码示例:

//二进制的写
int main()
{
	struct Stu s[2] = { {"张三", 20, 95.5} , {"lisi", 16, 66.5}};
	FILE* pf = fopen("data.txt", "wb");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//按照二进制的方式写文件
	fwrite(s, sizeof(struct Stu), 2, pf);
	//fwrite(s, sizeof(s), 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

由该函数的定义得,上述代码fwrite部分也可以这么写:

for (int i = 0; i < 2; i++)
	{
		fwrite(&s[i], sizeof(struct Stu), 1, pf);
	}

在这里插入图片描述

8.2 读文件

读二进制文件。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:

ptr:存放读取出来数据的内存空间
size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
stream:已经打开的文件指针

返回值:

成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
失败:0

代码示例:

int main()
{
	struct Stu s[2] = {0};
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL){
		printf("%s\n", strerror(errno));
		return 0;
	}
	//按照二进制的方式读文件
	fread(s, sizeof(struct Stu), 2, pf);
	//fread(s, sizeof(s), 1, pf);  一样的、
	printf("%s %d %lf\n", s[0].name, s[0].age, s[0].d);
	printf("%s %d %lf\n", s[1].name, s[1].age, s[1].d);

	fclose(pf);
	pf = NULL;
	return 0;
}

结果:
张三 20 95.500000
lisi 16 66.500000
请按任意键继续. . .

注意:保证读取文件数据总大小为:size * nmemb一样且合理即可。

九:文件的随机读写

9.1 fseek

int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数

stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节

返回值

成功:0
失败:-1

9.1 fteel

long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
参数

stream:已经打开的文件指针

返回值

成功:当前文件流(文件光标)的读写位置
失败:-1

9.1: rewind

void rewind(FILE *stream);

功能:把文件流(文件光标)的读写位置移动到文件开头。
参数

stream:已经打开的文件指针

返回值

无返回值

代码示例:

typedef struct Stu
{
	char name[50];
	int id;
}Stu;

//假如已经往文件写入3个结构体
//fwrite(s, sizeof(Stu), 3, fp);

Stu s[3];
Stu tmp; 
int ret = 0;

//文件光标读写位置从开头往右移动2个结构体的位置
fseek(fp, 2 * sizeof(Stu), SEEK_SET);

//读第3个结构体
ret = fread(&tmp, sizeof(Stu), 1, fp);
if (ret == 1)
{
	printf("[tmp]%s, %d\n", tmp.name, tmp.id);
}

//把文件光标移动到文件开头
//fseek(fp, 0, SEEK_SET);
rewind(fp);

ret = fread(s, sizeof(Stu), 3, fp);
printf("ret = %d\n", ret);

int i = 0;
for (i = 0; i < 3; i++)
{
	printf("s === %s, %d\n", s[i].name, s[i].id);
}

十:其他文件操作

10.1:获取文件状态

#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
功能:获取文件状态信息
参数

path:文件名
buf:保存文件信息的结构体

返回值

成功:0
失败-1

struct stat {
	dev_t         st_dev;         //文件的设备编号
	ino_t         st_ino;          //节点
	mode_t        st_mode;   //文件的类型和存取的权限
	nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1
	uid_t         st_uid;         //用户ID
	gid_t         st_gid;         //组ID
	dev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号
	off_t         st_size;        //文件字节数(文件大小)
	unsigned long st_blksize;   //块大小(文件系统的I/O 缓冲区大小)
	unsigned long st_blocks;    //块数
	time_t        st_atime;     //最后一次访问时间
	time_t        st_mtime;    //最后一次修改时间
	time_t        st_ctime;     //最后一次改变时间(指属性)
};

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char **args)
{
	if (argc < 2)
		return 0;

	struct stat st = { 0 };

	stat(args[1], &st);
	int size = st.st_size;//得到结构体中的成员变量
	printf("%d\n", size);
	return 0;
}

10.2:删除文件、重命名文件名

int remove(const char *pathname);
功能:删除文件
参数

pathname:文件名

返回值

成功:0
失败:-1

int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数

oldpath:旧文件名
newpath:新文件名

返回值

成功:0
失败: - 1

感谢大家的观看,有什么问题还请评论区指出,谢谢大家。
我们是冠军!!!!
在这里插入图片描述

  • 42
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 35
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值