彻底弄懂文件操作?——看这一篇就够了!!(c语言基础)

1.文件指针类型(FILE*)

先用一行代码让你理解文件类型指针(FILE*):

FILE *file = fopen("test.txt","r");

文件类型指针(FILE*)和其他指针类型一样,都是某一类型加上“ * ”后就变成指针类型了,同样的FILE类型在加上“ * ”前也是单独的一个类型,更确切的说,FILE是一个结构体类型。FILE结构体中包含了文件操作所需要的各种信息。那么我们就可以这样讲,一但一个文件类型指针指向一个fopen函数打开的文件,那么操作该文件所需要的各种信息就会储存在FILE结构体类型中。

那么现在再来看这一行代码,fopen函数以某种方式打开了指定文件并被文件指针file所接收,此时我们就拥有了操作该文件的一个“把手”(句柄或变量)file。这就是文件指针类型的意义所在,往后讲解的所有对文件的操作都要利用这个“把手”来完成。

2.fopen函数及6种常用的文件打开模式

fopen函数

fopen 是 C 标准库中的一个函数,用于 ​​打开文件并返回一个文件指针(FILE*)​​,以便后续进行读写操作。它的函数原型如下:

#include <stdio.h>

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

参数:filename以字符串形式接收文件名,mode是你要以什么样的方式来打开文件。

功能:

  • 打开指定路径的文件,并返回一个指向该文件的 FILE* 指针。
  • 如果打开失败,返回 NULL。
  • 文件指针用于后续的文件操作(如 fgetcfputc 等)。

返回值​:

​​成功​​:返回指向文件的 FILE* 指针。

失败​​:返回 NULL(如文件不存在、权限不足、路径错误等)。

文件打开模式

模式说明文件不存在时的行为文件存在时的行为
"r"只读模式返回 NULL(失败)从文件开头读取
"w"只写模式创建新文件清空文件内容(原有数据丢失)
"a"追加模式创建新文件在文件末尾追加数据
"r+"读写模式返回 NULL(失败)从文件开头读写(原有数据保留)
"w+"读写模式创建新文件清空文件内容(原有数据丢失)
"a+"读写模式创建新文件在文件末尾追加数据(读操作从文件开头开始)

这里的读写模式牵扯到后续fgetcfputc 等读写函数的使用,比如fgetc是读取一个字符,那么就用"r" "r+" "a+" "w+"等牵扯到可读模式来打开文件。

注意:

w模式和w+模式会导致数据丢失,意思是以该模式打开文件会清除文件内容并从头写入,因此当我们想要续写文件时,我们可以用a或a+模式来追加文件。但是r+模式有点特殊,因为以r+模式打开文件后我们也可以进行写入,它也是从头,但奇怪的是它原有数据不会丢失,即打开文件时不会清空文件内容,似乎和它的模式特点有点互相矛盾。但实际情况是:当使用 "r+" 打开文件时,操作系统不会清空文件内容。写入操作会直接覆盖指定位置的数据,但不会影响其他部分。

3.fgetc、fputc、fgets、fputs

当我们打开文件后,所有的写入和读取操作都能用这几个函数来完成。

1)fgetc

int fgetc(FILE *file);
功能:从file指针指向的文件中读取一个字符。
使用演示:
int ch;
while ((ch = fgetc(file)) != EOF) {
    printf("%c", ch);
}
返回值:

成功​​:返回读取的字符(转换为 int,范围 0~255)。

​​失败或 EOF​​:返回 EOF(通常是 -1)。

疑问:这是fgetc的基本工作机制,可是这里有一些疑问,为什么演示中的while循环可以正确执行下去而不陷入死循环?fgetc每次只是在读取一个字符,如果演示代码可以正确读出文件所有内容,说明一定存在某些标记可以记录并更新fgetc读取文件的位置,这里我们先按下不表。

2)fputc

int fputc(int char, FILE *file);
功能:将指定的字符写入到file指针指向的文件中。
使用演示:
fputc('A', file);
返回值:

​​成功​​:返回写入的字符(0~255)。

失败​​:返回 EOF(通常是 -1)。

问疑:同样的,如果连续输入十次演示代码,文件中会被写入写入十个 “A” ,似乎这十个fputc函数像是商量好的一样将 “A” 连续挨着写入。和fgetc的疑问一样我们先按下不表。

 3)fgets

char *fgets(char *str, int size, FILE *file);
功能:

从指定的file指向的中读取 ​​最多 size - 1 个字符​​,并存储到 str 指向的缓冲区(就是str指向的存储字符串的空间)。

如果遇到 ​​换行符 \n​​​​文件结束(EOF)​​,则停止读取,并在字符串末尾自动添加 '\0'(字符串结束符)。

演示:
char buffer[100];  // 缓冲区大小为 100
fgets(buffer, sizeof(buffer), stdin);

成功​​:返回 str(指向读取的字符串)。

失败或 EOF​​:

        如果未读取到任何字符(如 size <= 0 或文件为空),返回 NULL。

        如果读取到部分数据(如遇到 EOF 但已读取部分字符),仍然返回 str(但下次调用会返回 NULL)。

疑问:

当我们连续的用fgets读取文件中的内容时:

	FILE* file = fopen("test.txt", "r");
	char buffer[100];
	fgets(buffer, sizeof(buffer), file);
	printf("%s", buffer);
	
	fgets(buffer, sizeof(buffer), file);
	printf("%s", buffer);
	
	fgets(buffer, sizeof(buffer), file);
	printf("%s", buffer);

文件中的内容:

输出:

可以看到每次读取的内容都会覆盖原有的缓冲区buffer,且每次fgets的读取都会很默契的读取下一行,而不是重复读取第一行,那么这里的疑问和fgetc以及fgets一样,先按下不表。

4)fputs

int fputs(const char *str, FILE *stream);
功能:

将字符串 str 写入到file指向的文件中。

不包含字符 \0 也不会添加 \n (多次写入的内容在文件中是连续的)

演示:
FILE* file = fopen("test.txt", "w");
const char* buffer1 = "我是第一行";
const char* buffer2 = "我是第二行";
const char* buffer3 = "我是第三行";
fputs(buffer1,file);
fputs(buffer2,file);
fputs(buffer3,file);

文件夹显示;

可以看到内容是连续的。

返回值:

成功​​:返回非负整数(通常返回写入的字符数,但标准未明确规定)。

失败​​:返回 EOF(通常是 -1)。

疑问:

这里的疑问同上,所有的疑问将会在文件定位中解答

4.文件定位

在讲读写函数时,有一个统一的疑问。那就是为什么这些函数能够自动定位文件内容,其实答案很简单,这些读写函数都有一个共同点,它们的参数中都含有文件指针,而文件指针前面讲过,他是指向一个结构体的指针,而这个结构体中包含了文件的各种信息,虽然这个结构体的具体实现由编译器/标准库决定,但在大多数实现中,它包含了一个关键的成员——​​文件位置指示器。  这里我们不具体展开讲,只需通俗的理解文件指针会记录文本位置信息,即每一次使用读写函数时,它会根据该文本位置信息进行操作。

疑问:当我们对一个文本文件进行文本写入后,再对该文件进行读取,会出现什么情况?

以下代码是对一个文件进行写入操作后再进行读取:

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

int main() {
    //以读写的方式打开文件
	FILE* file = fopen("test.txt", "w+");
	if (file == NULL) {
		perror("fopen");
		exit(1);
	}

    //向文件中写入 hello world\n
	const char *buffer1 = "hello world\n";
	int n = fputs(buffer1,file);
	if (n == -1) {
		perror("fputs");
		exit(2);
	}

	/*int m = fseek(file, 0, SEEK_SET);
	if (m == -1) {
		perror("fseek");
		exit(3);
	}*/

    //读取文件中的一行
	char buffer[100];
	char* s = fgets(buffer, sizeof(buffer), file);
	if (s == NULL) {
		perror("fgets");
		exit(4);
	}

	printf("%s", buffer);

	fclose(file);

	return 0;
}

运行效果图:

图中什么也没有打印,表明fgets并没有读取到任何字符。这是为什么呢?

不卖关子,原因就是因为fgetc函数已经更新了file文件指针中的位置信息,再使用fgets函数对文件进行读取时,读取的起始位置就是文本的末尾,导致什么也没有读到。

为了解决类似问题,我们便可以引出文件定位函数,可以帮助我们手动对文件的读写位置进行定位。下面我们利用文件定位函数fseek来解决疑问中的问题。

取消对fseek函数的注释:

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

int main() {
    //以读写的方式打开文件
	FILE* file = fopen("test.txt", "w+");
	if (file == NULL) {
		perror("fopen");
		exit(1);
	}

    //向文件中写入 hello world\n
	const char *buffer1 = "hello world\n";
	int n = fputs(buffer1,file);
	if (n == -1) {
		perror("fputs");
		exit(2);
	}

    ///////////////////////////////////////////
    //取消对定位函数fseek的注释
	int m = fseek(file, 0, SEEK_SET);
	if (m == -1) {
		perror("fseek");
		exit(3);
	}
    ////////////////////////////////////////////

    //读取文件中的一行
	char buffer[100];
	char* s = fgets(buffer, sizeof(buffer), file);
	if (s == NULL) {
		perror("fgets");
		exit(4);
	}

	printf("%s", buffer);

	fclose(file);

	return 0;
}

运行效果:

这里fseek的作用就是将文件指针重新定位到文件开头,使读取函数能够从文件开头读取。

文件定位常用函数:

1)fseek

int fseek(FILE *stream, long offset, int whence);

功能​​:移动文件流 stream 的指针到指定位置。

参数​​:

stream:文件指针(由 fopen() 返回)。

offset:偏移量(字节),可为正(向后)或负(向前)

whence:基准位置:

    SEEK_SET:文件开头

    SEEK_CUR:当前位置

    SEEK_END:文件末尾

返回值​​:

成功:0

失败:非零(并设置 errno)

代码示例:

FILE *fp = fopen("file.txt", "r");
fseek(fp, 100, SEEK_SET);  // 移动到第 100 字节
fseek(fp, -50, SEEK_CUR);  // 当前位置向前移动 50 字节
fseek(fp, 0, SEEK_END);    // 移动到文件末尾
fclose(fp);

2)ftell

long ftell(FILE *stream);

功能​​:返回文件流 stream 的当前指针位置(相对于文件开头的偏移量)。

返回值​​:

成功:当前偏移量(long 类型)

失败:-1L(并设置 errno

代码示例:

FILE *fp = fopen("file.txt", "r");
fseek(fp, 0, SEEK_END);
long size = ftell(fp);  // 获取文件大小
fclose(fp);
printf("File size: %ld bytes\n", size);

3)rewind

void rewind(FILE *stream);

功能​​:将文件指针重置到文件开头,并清除 EOF 标志和错误标志

​等效于​​:

fseek(stream, 0, SEEK_SET);
clearerr(stream);

代码示例:

FILE *fp = fopen("file.txt", "r");
fseek(fp, 100, SEEK_SET);
rewind(fp);  // 回到文件开头
fclose(fp);

6.总结

文件操作是程序与外部数据交互的核心环节,C语言层面上的文件操作,本质还是对系统文件操作函数的封装。学好 C 语言层面的文件操作,是理解 Linux 等操作系统底层机制的重要基石。它不仅能为你后续的系统级编程打下坚实基础,还能培养对程序与硬件资源交互的直觉。

“C 文件操作是打开操作系统黑盒的钥匙——你写的每一行 fread,最终都会变成内核中的一段汇编。”​​ 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值