【C语言笔记】C语言文件操作全解

一、文件的打开和关闭

我们在编写或运行C语言程序时总会产生或多或少的数据,如果我们想让这些数据能够长久的使用,就可以将它们保存到文件中。
在程序设计中,我们所谈到的文件一般分为两种:程序文件和数据文件,而我们今天所要讲的文件操作主要针对的是数据文件。

1、文件指针

在讲文件操作之前,必须先清楚的一个概念就是“文件指针”。
顾名思义,“文件指针”就是一个“指向文件的指针”
关于文件指针的来历,其实是这样的:

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE.

而我们所说的“文件指针”其实指向的也并非文件本身,而是文件所的对应的文件信息区的结构体变量。
所以我们在操作文件的时候其实是通过文件指针找到了一个文件信息区变量,在通过这个变量取得文件的地址,在访问文件,实际属于间接间接访问了:
在这里插入图片描述

2、“流”的概念

在程序设计中,“流”是一个高度抽象的概念,它的出现主要是为了解决效率问题。设想一下我们程序员在编写程序时候,可能用把程序所产生的数据文件存储到不同格式的文件之中,这些格式不同的文件的读写方式当然也是不同的,那么这岂不是要我们程序员要把这些读写方式全都掌握呢?这也太麻烦了吧。
所以,为了调效率,我们才抽象出了“流”的概念,“流”其实可以粗略的理解为是一个统一的通道,它把各种输入的数据统一汇集到它的通道中,然后再经过要求将数据以各种格式输入到各种格式的文件中:
在这里插入图片描述
所以我们程序员在编写程序的时候,只需要关心把信息写入到流中即可,置于流是怎么把数据写入各种文件中的,我们不需要关心。
所以我们之前的文件指针就可以称为是一个“流”,因为我们程序员的输入格式可能和真正要写入文件的格式是不同的。
所以在大多数的文档中对于文件指针更多的是称其为“流”,而不是直接称为文件指针,这个也是要事先说清楚的。

3、fopen和fclose函数

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
其中打开的形式如下:

FILE * fopen ( const char * filename, const char * mode );

其中filename指的是你要打开的文件的文件名,mode是你将要以什么形式打开,这些形式有很多种,这里就不一一列举了,后面遇到再细讲。
其实就像动态内存申请一样,我们文件的打开也也有失败的时候,如果打开失败,那么fopen函数就将返回一个空指针NULL。
所以对文件打开的结果进行判断也是很重要的:

int main() {
	FILE* pf = fopen("test.txt", "w"); // "w"表示以读的形式打开
	if (NULL == pf) {
		perror("fopen");
		return -1;
	}
	return 0;
}

关闭文件的形式如下:

int fclose ( FILE * stream );

我们只需要传入一个文件指针(流)即可。
因为文件也是一种资源,而资源总归是有限的,不可能让你只是用而不回收,所以在使用完文件后就关闭文件也是很重要的:

int main() {
	FILE* pf = fopen("test.txt", "w"); 
	if (NULL == pf) {
		perror("fopen");
		return -1;
	}
	// 写文件
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

二、文件的顺序读写

打开文件后,我们就可以对文件进行读写操作了。

1、fputc和fgetc函数

fputc函数的功能是每次向文件中写入一个字符,使用这个函数我们需要传入两个参数,第一个是要写入的字符,第二个就是文件流:
在这里插入图片描述
比如我们想依次向文件中写入“hello”这个单词,就可以用fputc函数分5次写入:

int main() {
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf) {
		perror("fopen");
		return -1;
	}
	// 写文件
	fputc('h', pf);
	fputc('e', pf);
	fputc('l', pf);
	fputc('l', pf);
	fputc('o', pf);
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

而当我们以“w”的形式打开文件时,如果文件不存在,那么程序就会自动自当前路径地下创建一个文件:
在这里插入图片描述
所以当我们的程序运行时,就可以发现当前路径下就自动创建了一个名为test.txt的文件:
在这里插入图片描述
我们打开它,就可以看到里面已经是有内容的了:
在这里插入图片描述
而fgetc函数正好是与fputc相反的,即为每次从文件中读出一个字符,使用这个函数我们就只需要传入一个参数文件流即可:
在这里插入图片描述
例如我们想把刚刚存进去的“hello”逐个字符的读出来,就可以这样做:

int main() {
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf) {
		perror("fopen");
		return -1;
	}
	// 读文件
	char ch = 0;
	ch = fgetc(pf);
	printf("%c ", ch);
	ch = fgetc(pf);
	printf("%c ", ch);
	ch = fgetc(pf);
	printf("%c ", ch);
	ch = fgetc(pf);
	printf("%c ", ch);
	ch = fgetc(pf);
	printf("%c ", ch);
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

而这次我们是以“r”——只读的形式打开的文件,该方式如果文件不存在的话就会直接报错:
在这里插入图片描述
而我们的test.txt是已经创建好了的,所以我们运行程序后,屏幕上就会出现这样的结果:
在这里插入图片描述

2、fputs和fgets函数

fputs函数的功能是每次向文件中写入一个字符串,使用这个函数我们需要传递两个参数,第一个是一个字符串(其实是字符串起始字符的地址),第二个就是文件流:
在这里插入图片描述
例如我们想象杠杠的test.txt文件中写入“hello world!”这就话,就可以这样做:

int main() {
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf) {
		perror("fopen");
		return -1;
	}
	// 写文件
	fputs("hello world!", pf);
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

这里就要先说一下以“w”只读方式打开文件的一个特性了。
以此方式打开文件时,每次打开不管文件中有多少内容,都会先将原有的内容全清空,然后再重新向里面写入数据。
所以当程序执行完毕之后再打开test.txt就会发现内容变成了“hello world”,之前的“hello”就不见了:
在这里插入图片描述
而fgets也就是每次从文件中读出一个字符串,使用这个函数我们需要传入三个参数,第一个是读到的字符串将要被拷贝到的空间的起始地址,第二个是每次将要读取的字符的个数,第三个才是文件流:
在这里插入图片描述
该函数的返回值是一个char类型的指针,也就是第一个字符的地址,所以我们在调用此函数的时候需要用一个char*类型的变量来接收其返回值。
而这个函数还有一点需要注意,就是这个函数在每次读取的时候都会默认再添加一个字符串结束标志‘\0’,而这个‘\0’也会占用一个个数,也就是说我实际要传参传入的个数要比我们实际想要读到的字符的个数多一个。
例如我们想要将已存进test.txt文件中的“hello world!”全部读则需要传递个的个数是13而不是12:
在这里插入图片描述
如果传入的是12,那就会少了一个字符:
在这里插入图片描述

3、fwrite和fread函数

以上的函数都是以文本的形式读写文件,也可以说是以字符的形式读写文件。但读写文件的方式可以多种多样的,fwrite和fread函数就是以二进制的形式进行读写文件,也就是将内存中的数据(内存中的数据全都是以二进制的形式存储的)直接写入文件中,不做任何转换。
以二进制的形式向文件中写入信息的函数时fwrite,使用该函数时我们需要传入4个参数:
在这里插入图片描述
这参数好像有点儿多了,让我们一个一个的来看吧:
1、ptr: 指向源数据首地址的指针(要写的数据来自哪里)。
2、size: 要写的每一个元素的大小。
3、count: 总共要写多少个元素。
4、stream: 将要被写入数据的流。

这样好像还是难以理解,我们举个例子吧:

int main() {
	FILE* pf = fopen("test.txt", "wb"); // wb表示以二进制写入的方式打开
	if (NULL == pf) {
		perror("fopen");
		return -1;
	}
	// 写文件
	char str[20] = "no overtmie";
	fwrite(str, sizeof(char), strlen(str), pf);
	// 要写的数据从str这个数组中来
	// 每个元素的大小是1个字节
	// 一共写strlen(str)个元素
	// 写入到pf所指向的文件中
	
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

写完后我们可以看看test.txt的内容:
在这里插入图片描述
有些人可能就会认为,这好像和直接使用文本写入也没什么区别啊。
其实这是因为这里所写入的是字符啊,我们知道字符类型的本质是ASCLL码值啊,ASCLL码在内存中就是以二进制的形式存储的。所以这里现实的彩绘和文本写入的相同。
当要是我们写入的是一个结构体的数据,那结果就不同了:

struct boy {
	int id;
	int age;
	char name[10];
};

int main() {
	FILE* pf = fopen("test.txt", "wb"); // wb表示以二进制写入的方式打开
	if (NULL == pf) {
		perror("fopen");
		return -1;
	}
	// 写文件
	struct boy b = { 1, 20, "zhangsan" };
	fwrite(&b, sizeof(b), 1, pf);
	
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

程序运行结束后我们再来看看test.txt中的内容就看不懂了:
在这里插入图片描述
但我们看不懂没关系,计算机能看懂就行了。比如fread函数就能看懂。
fread函数的功能就是以二进制的方式从文件中读出数据,使用该函数时,我们也还是需要传入4个参数:
在这里插入图片描述
1、ptr: 指向读出的数据将要被放到的内存的起始地址(读出的数据要放到哪里)。
2、size: 要读出的每一个元素的大小。
3、count: 总共要读出多少个元素。
4、stream: 将要被读出数据的流。
比如我们可以将之前存入文件中的“no overtime”这个字符串在从文件中读出来:
在这里插入图片描述

三、文件的随机读写

之前介绍的函数都只能按顺序的读写文件,但C语言给我们提供了一些函数,这些函数可以使我们的文件指针来回地移动,从而实现文件的随机读写。

1、fseek函数

fseek函数的功能是根据文件指针的位置和偏移量来定位文件指针。也就是从当前文件指针的位置开始,将文件指针移动到对应偏移量的位置。
这个函数一共有三个参数:
在这里插入图片描述
第一个参数stream就是文件流,第二个参数offset就是偏移量,第三个参数origin就是起始位置。
而且该函数规定了origin的取值只能由三种情况:
在这里插入图片描述
我们可以使用fseek函数对指针就行定位后在进行读取,比如我们现在的test.txt中有以下内容:
在这里插入图片描述
若是我想一开始就从第4个字符开始读取,就可以这样做:
在这里插入图片描述

2、ftell函数

ftell函数的功能是返回当前文件指针相对于起始位置的偏移量。例如上一个例子执行完毕后,pf相对于起始位置的偏移量应该是4,我们可以将它打印出来看看:
在这里插入图片描述

3、rewind函数

rewind函数的功能是将文件指针重新指向文件的起始位置,当我们操作的过多或者过复杂的时候,就可以使用这个函数来完成一个“归零”,例如我们紧接着上例,就可以将文件指针先“归零”后在读取一次,看看它打印的是不是第一个字符:
在这里插入图片描述

四、文件读取结束的判定

文件的大小一定是有限的,那么就一定会有文件读取结束的情况。

1、判断EOF或者NULL

判断文件读取是否结束常用的方法就是判断返回值是否是EOF或者NULL。
判断EOF是相对于fgetc函数而言的,我们可以去相关文档去看看关于fgetc函数返回值的介绍:
在这里插入图片描述
上面清晰地说明着,如果文件指针已经到了文件的末尾,就会返回EOF。
而判断是否为NULL是相对于fgets函数而言的,我们也可以去相关的文档中翻一翻关于这个函数返回值的介绍:
在这里插入图片描述
上面也说了如果读取失败或出现错误,则返回空指针。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林先生-1

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

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

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

打赏作者

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

抵扣说明:

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

余额充值