
🔥个人主页:艾莉丝努力练剑 🍓专栏传送门:《C语言》 🍉学习方向:C/C++方向 ⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平

前言:前面几篇文章介绍了c语言的一些知识,包括循环、数组、函数、VS实用调试技巧、函数递归、操作符、指针、字符函数和字符串函数、C语言内存函数、数据在内存中的存储、结构体、联合和枚举、动态内存管理等,在这篇文章中,我将开始介绍文件操作的一些重要知识点!对文件操作感兴趣的友友们可以在评论区一起交流学习!
我们为什么要使用文件?在介绍文件操作之前,我们先要知道为什么要使用文件。我们写的程序的数据是保存在电脑的内存中的,如果没有文件,一旦程序退出,内存被操作系统回收,数据就丢失了,等到我们再次运行程序,就看不到上次程序的数据了!如果想要数据持久化保存,可以使用文件。这就是为什么我们要使用文件了。
硬盘(磁盘,也叫外存)上的文件就叫文件——用文件来解释文件啦哈哈。
在程序设计中,我们从文件功能的角度把文件分成了两类:程序文件和数据文件。
程序文件分为三类:源程序文件(后缀名为.c)、目标文件(Windows环境后缀名为.obj)、可执行文件(Windows环境后缀名为.exe)。
文件内容不一定是程序,也可以是程序运行时读写的数据,比如程序运行时需要从中读取数据的文件,或者输出内容的文件。我们这里主要介绍的是数据文件。
之前我们介绍的C语言内容所处理的数据的输入输出均是以终端(输入输出设备)为对象的,就是从终端的键盘输入数据,运行结果显示到显示器(电脑屏幕)上面,但有时候我们会把信息输入到磁盘上,需要的时候再从磁盘读取到内存中使用,这里处理的就是磁盘上文件。
一个文件要有唯一的文件标识,以便用户识别和引用。
文件名包含三部分:文件路径、文件名主干、文件名后缀,文件名就是文件路径+文件名主干+文件名后缀,比如,C:\Users\15306\Desktop.txt就是一个文件名。
为了方便起见,文件标识常被称为文件名。
根据数据的组织形式,数据文件分为二进制文件和文本文件。
数据在内存中以二进制的形式存储,如果不加转换地输出到外存(磁盘)的文件中,就是二进制文件。 如果要求在外存上以ASCLL码的形式存储,则需要在存储前转换,以ASCLL字符的形式存储的文件就是文本文件。
一个数据在文件中是怎么存储的呢?
字符一律是用ASCLL形式存储;数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
假如有个整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而如果以二进制形式输出,在磁盘就只占4个字节。

#include<stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
fclose(pf);
pf = NULL;
return 0;
}这里的.txt就是文件名后缀,wb我们会在【文件打开的操作方式】 部分介绍,这里我们的关注点先放在二进制文件上。
我们在文件夹里面找到"test.txt"的文件:

我们以记事本形式为打开方式,发现是一个奇怪的乱码:

那怎么打开呢?
在VS上打开二进制文件的方法:

如下图,10000在二进制文件中是这样的:

这真的是10000吗?又是怎么得出来的呢?见下图:

十六进制的10即二进制的00010000,即十进制的16。
这个模块我们主要得掌握流、文件指针、fopen(文件的打开)、fclose(文件的关闭)。
概念认识:程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行操作,才抽象出了流的概念,流你可以想象成一条条流淌着字符的河。
C语言针对文件、画图、键盘等的数据输入输出操作都是通过流来操作的。
一般情况下,我们要想往流里面写进数据,或是从流中读取出数据,都是要打开流,再操作。
既然如此,为什么我们在键盘上输入数据,向屏幕上输出数据,却没有打开流哩?这是因为C语言程序在启动的时候就默认打开了3个流,分别是:
stdin:标准输入流(键盘),在大多数环境中是从键盘输入,比如scanf函数就是从标准输入流中读取数据的。 stdout:标准输出流(屏幕),在大多数环境中输出到显示器界面,比如printf函数就是将信息输出到标准输出流中的。 stderr:标准错误流,在大多数环境中输出到显示器界面。
这三个流是默认打开的,我们使用scanf、printf等函数就可以直接进行输入输出操作了。
这三个流的类型是 FILE* ,通常下称为文件指针。我们下面会详细介绍。
C语言中,程序员就是通过 FILE* 的文件指针来维护流的各种操作的。
缓冲文件系统中,关键的概念是“文件类型指针”,即“文件指针”。
每个被使用的文件都会在内存开辟一个相应的文件信息区,用来存放文件的相关信息(比如文件的名字、文件状态以及文件当前的位置等)。这些信息是保存在一个结构体变量中的,这个结构体的类型是由系统声明的,取名为FILE。
不同的C语言编译器的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;每当我们打开一个文件,系统会根据文件的情况自动创建一个FILE结构的变量 ,并且会填充其中的信息,我们使用者不用担心,现阶段没必要关心其细节,只要知道怎么用就可以了。
一般是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来会更加方便。
下面这个就是FILE*的指针变量:
FILE* pf;这个是文本指针变量。
定义pf为一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文本信息区(是个结构体变量)。通过这个文件信息区中的信息就能够访问这个文件了,也就是说,通过文件指针变量能够间接找到与其相关联的文件。
打个比方,就比如下图:

前文说了,我们用fopen函数来打开文件,用fclose函数来关闭文件。下面我们详细介绍一下:
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,打开文件的同时,都会返回一个FILE*的指针变量,指向该文件,相当于建立了指针与文件的关系。
前文说的fopen打开文件、fclose关闭文件是ASCII规定的。
下面我详细介绍一下这两个函数:
下面我们以打开data.txt文件为例,这里的"w"是指为了输出文件而打开一个文本文件:
//打开文件
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
pf = NULL;
}这里我们就能看出fopen函数的原型:
FILE* fopen(const char* filename, const char* mode);我们从功能、参数、返回值的角度来分析一下fopen函数:
功能:fopen函数是用来打开参数filename指定的文件,同时将打开的一个文件和一个流进行关联,后续对流的操作是通过fopen函数返回的指针来维护,具体对流(关联的文件)的操作是通过参数mode来指定的。 参数: filename:表示被打开的文件的名字,这个名字可以是绝对路径,也可以是相对路径; mode:表示对打开的文件的操作方式,下面我绘制了一幅表格,具体见表格。 返回值: (1)若文件成功打开,该函数将返回一个指向FILE对象的指针,该指针可用于后续操作中标识对应的流; (2)若文件打开失败,则返回NULL指针,因此一定要对fopen的返回值做判断,来验证是否打开成功。
下面是完整代码演示:
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r"); //以“r”的形式打开文件,如果文件不存在,则打开失败
if (pf == NULL)
{
perror("fopen\n");
return 1;
}
printf("打开文件成功,可以对文件进行操作\n");
//不再使用文件时,需要关闭⽂件
return 0;
}当然,我们这里再补充一些知识点,看下面这段代码:
#include<stdio.h>
int main()
{
//.——表示当前路径
//..——表示上一级路径
FILE* pf = fopen("data.txt", "r");
/*FILE* pf = fopen("./../data.txt", "r");*/
//这里的路径是相对路径,这表示在当前的工程目录下的data.txt
//FILE* pf = fopen("C:/Users/18106/Desktop/data.txt", "r");
/*FILE* pf = fopen("C:\\Users\\18106\\Desktop\\data.txt", "r");*/
//这是绝对路径的写法
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}由于在前面的fopen环节我们已经用"w"建立了一个新的文件"data.txt",所以这里我们已经可以读取文件了(如果选择打开一个已经存在的文件,该指定文件却不存在,就会出错),即"r"。
当文件不在当前路径下的时候我们就不能直接这样写了:
FILE* pf = fopen("data.txt", "r");要是把路径要写出来:
‘.’——表示当前路径; ‘..’—— 表示上一级路径。
上面我展示了相对路径和绝对路径的写法 这里拎下来我们再详细介绍一下:
我们先看看路径,还是data.txt文件,我们在文件夹里查看一下它的当前路径:


这个就是data.txt文件的当前路径。那怎么写呢?我上面说了,我们用'.'表示当前路径,用'..'表示上一级路径,假如我们把文件像这样挪到了上一级路径下:

像这种情况我们写出其相对路径:
以下是相对路径的写法——
FILE* pf = fopen("./../data.txt", "r");
//这里的路径是相对路径,这表示在当前的工程目录下的data.txt同理,如果再往前挪一个路径,就在后面再加一个'..'就可以了,像这样:

FILE* pf = fopen("./../../data.txt", "r");如果我们把文件拖到桌面上呢?该怎么写?这时候我们就不能写相对路径了,我们应该写绝对路径,绝对路径是从根上写的,那哪个是绝对路径哩?我们可以在属性里面查看,复制下来即可:

绝对路径也有两种写法,可以用'/'写,反过来写也可以,但要写两个,即 '\\',如果写一个可能会跟后面的字符构成转义,就不方便了:
以下是绝对路径的写法——
FILE* pf = fopen("C:/Users/18106/Desktop/data.txt", "r");
FILE* pf = fopen("C:\\Users\\18106\\Desktop\\data.txt", "r");
//这是绝对路径的写法完整写法:
#include<stdio.h>
int main()
{
//.——表示当前路径
//..——表示上一级路径
/*FILE* pf = fopen("data.txt", "r");*/
/*FILE* pf = fopen("./../data.txt", "r");
FILE* pf = fopen("./../../data.txt", "r");*/
//这里的路径是相对路径,这表示在当前的工程目录下的data.txt
FILE* pf = fopen("C:/Users/18106/Desktop/data.txt", "r");
FILE* pf = fopen("C:\\Users\\18106\\Desktop\\data.txt", "r");
//这是绝对路径的写法
if (pf == NULL)
{
perror("fopen");
return 1;
}
return 0;
}此外,我们还要了解mode表示对打开的文件的操作方式:
文件使用方式 | 含义 | 如果指定文件不存在 |
|---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件末尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件末尾添加数据 | 建立一个新的文件 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建立一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行续写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写,打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行续写 | 建立一个新的文件 |
有打开就得有关闭,像刚才的data.txt文件,我们使用完应该关闭它,关闭文件就比如:
//关闭文件
fclose(pf);
pf = NULL;fclose函数的函数原型是这样的:
int fclose ( FILE * stream );按照功能、参数、返回值的三板斧,我们对fclose函数抽丝剥茧:
功能:关闭参数stream关联的文件,并且取消其关联联系。同该流相关联的所有内部缓冲区均会解除关联,并且刷新:任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区内容将被丢弃; 参数:stream:指向要关闭的流的FILE对象的指针; 返回值: 成功关闭stream指向的流会返回0,否则会返回EOF。
下面是完整版代码演示:
#include<stdio.h>
//打开文件
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
pf = NULL;
}
//写文件
//......
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}配注释版代码演示:
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "r"); //以“r”的形式打开文件,如果文件不存在,则打开失败
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
printf("打开文件成功,可以对文件进行操作\n");
//不再使用文件时,需要关闭文件
fclose(fp);
fp = NULL; //将指针置为NULL,避免成为野指针。
return 0;
}文件顺序读写时会涉及一些函数,我放在表格里面了:
函数名 | 功能 | 适用于 |
|---|---|---|
fgetc | 从输入流中读取一个字符 | 所有输入流 |
fputc | 向输出流中写入一个字符 | 所有输出流 |
fgets | 从输入流中读取一个字符串 | 所有输入流 |
fputs | 向输出流中写入一个字符串 | 所有输出流 |
fscanf | 从输入流中读取带有格式的数据 | 所有输入流 |
fprintf | 向输出流中写入带有格式的数据 | 所有输出流 |
fread | 从输入流中读取一块数据 | 文件输入流 |
fwrite | 向输出流中写入一块数据 | 文件输出流 |
上面说的适用于所有输入流一般指适用于标准输入流(键盘)和其他输入流(如其他输入流);所有输出流一般指适用于标准输出流(屏幕/显示器)或其他输出流(如文件输出流)。
接下来我会为友友们一一介绍这几个函数,每个函数在介绍时我都会放上代码实现!
代码演示:
#include<stdio.h>
//打开文件
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
pf = NULL;
}
//写文件
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
fputc('d', pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}我们可以直接写字符到屏幕(显示器)上:
#include<stdio.h>
int main()
{
//写字符到标准输出流(屏幕)上
fputc('a', stdout);
fputc('b', stdout);
fputc('c', stdout);
fputc('d', stdout);
return 0;
}这样写abcd四个字符就会打印到屏幕上了:

有没有感觉和我们之前讲过的putchar有异曲同工之妙?我们写出来看看:
#include<stdio.h>
int main()
{
//写字符到标准输出流(屏幕)上
putchar('a');
putchar('b');
putchar('c');
putchar('d');
return 0;
}我们可以利用fputc这个函数让它在文本文件data.txt里面写出26个英文字母:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
int i = 0;
for (i = 'a'; i <= 'z'; i++)
{
fputc(i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}如下图所示:

看完了代码,我们再来观察函数原型就是手拿把掐了:
fputc函数原型——
int fputc(int character, FILE* stream);三板斧——
功能:将函数character指定的字符写入到stream指向的输出流中,通常用于向文件或者标准输出流写入字符,而且在写入字符之后,还会调整指示器(光标)。字符会被写入内部位置指示器当前指向的位置,随后该指示器会自动向前移动一个位置(视觉上应该是每写入一个字符,光标自动移动到这个字符后面) 参数: character:被写入的字符; stream:是一个FILE*类型的指针,指向了输出流(一般是文件流或者stdout,stdout在前面写字符到屏幕上那里已经露过脸了,stdout就是标准输出流,即屏幕/显示器,反之,stdin就是标准输入流,即键盘)。 返回值: (1)成功时返回写入的字符(以int形式); (2)失败时返回EOF(EOF通常为-1),错误指示器会被设置,可通过ferror()检查具体错误。
EOF和ferror()博主在后面会介绍。
代码演示:
#include<stdio.h>
//读文件
int main()
{
FILE* pf = fopen("data.txt","r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}我们可以结合fputc和getchar,看看它们分别会输出什么样的结果:
#include<stdio.h>
//读文件
int main()
{
FILE* pf = fopen("data.txt","r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);
putchar(ch);
fputc(ch, stdout);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}输出结果:

这里没有换行 ,因为putchar打印完不会换行。这三种我们采用任意一种就可以了,一次采用一个就可以了。
我们可以多打印几个,之前我们打印了26个英文字母的符号,我们可以读10个,读完一个打印一个,光标跳到下一位,代码演示如下:
#include<stdio.h>
//读文件
int main()
{
FILE* pf = fopen("data.txt","r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int i = 0;
for (i = 0; i < 10; i++)
{
int ch = fgetc(pf);
fputc(ch, stdout);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}输出结果:

我们也可以从键盘上读取数据,之前介绍了,叫“stdin” :
#include<stdio.h>
//从键盘读取数据
int main()
{
//读文件
int i = 0;
for (i = 0; i < 10; i++)
{
int ch = fgetc(stdin);
fputc(ch, stdout);
}
return 0;
}这里设置好打印10个字符, 超过10个时仍能打印,打印的是前10个:

如果第一次只打印了5个字符,程序不会退出,直到打印够10个字符为止。
那可不可以在键盘上读取数据一次只读一个呢?代码演示:
#include<stdio.h>
//从键盘读取数据——一次读一个
int main()
{
//读文件
int i = 0;
int ch = fgetc(stdin);
fputc(ch, stdout);
return 0;
}这样就能实现一次只读一个了,不管你一次输入多少个字符都只读第一个:

注意:EOF(end of file):文件结束标志。我们之前也讲过一个结束标志:字符串的结束标志(\0),学习语言的过程中要温故而知新。
我们先来看代码原型:
int feof(FILE* stream);
//检测stream指针指向的流是否遇到文件末尾
int ferror(FILE* stream);
//检测stream指针指向的流是否发生读/写错误如果在读取文件的过程中,遇到了文件末尾,文件读取就会结束,这时候读取函数会在流上设置一个文件结束的指示符,这个文件结束指示符可以通过feof函数检测到。如果feof函数检测到文件结束指示符已经被设置,就返回非0的值,如果没有设置则返回0.
如果在读/写文件的过程中,发生了读/写的错误,文件读取就会结束。此事读/写,函数会在对应的流上设置一个错误指示符,这个错误指示符可以通过ferror函数检测到。如果ferror函数检测到错误指示符已经被设置,则返回非0的值,如果没有设置就返回0.
代码演示:
#include <stdio.h>
//假设test.txt文件中存放abcdef
int main()
{
FILE* fp = fopen("data.txt", "r");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
int c = fgetc(fp);
if (c == EOF)
{
if (feof(fp))
printf("遇到文件末尾了\n");
else if (ferror(fp))
printf("读取发生了错误\n");
}
else
{
fputc(c, stdout); //使用fputc在标准输出流上打印字符
}
}
//不再使用文件时,需要关闭文件
fclose(fp);
fp = NULL; //将指针置为NULL,避免成为野指针。
return 0;
}如果我们以写的形式打开文件,就会发生错误:
#include<stdio.h>
//以写的形式打开文件后,再去读文件,就会发生错误
int main()
{
FILE* fp = fopen("test.txt", "w");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
int c = fgetc(fp);//读文件
if (c == EOF)
{
if (feof(fp))
printf("遇到文件末尾了\n");
else if (ferror(fp))
{
printf("读取发生了错误\n");
}
}
else
{
fputc(c,stdout);//使用fputc在标准输出流上打印字符
}
//不再使用文件时,需要关闭文件
fclose(fp);
fp = NULL;//将指针置为NULL,避免成为野指针
return 0;
}结果如下:

函数原型:
int fputs(const char* str, FILE* stream);三板斧——
功能:将参数str指向的字符串写入到参数 stream 指定的流中(不包含结尾的空字符 \0 ),适用于文件流或标准输出(stdout)。 参数: str:str是指针,指向了要写入的字符串(必须以 \0 结尾) stream :是一个 FILE* 的指针,指向了要写入字符串的流。 返回值: (1)成功时返回非负整数。 (2)失败时返回EOF (-1),同时会设置流的错误指示器,可以使用ferror() 检查错误原因。
代码演示:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputs("abc", pf);
fputs("def", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}这样写出来是一行,我们在"abc"后面加上一个“\n”就变成两行了:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputs("abc\n", pf);//这样写进去就变成两行了
fputs("def", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}前面都是写入到data.txt文件里,只要把pf改成stdout,我们可以让它打印到屏幕上:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputs("abc\n", pf);//写进去变成两行了
fputs("def", stdout);//打印到屏幕上去了
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}这样一来,def就打印到屏幕上了。
我们先演示一段代码——
代码演示:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[20] = ("xxxxxxxxxxxxxxxx");
fgets(arr, 20, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}代码这样写可以让我们在监视窗口明显观察到fgets的特点。
把fgets(arr,20,pf); 的20改成5,就变成读5个字符了:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[20] = ("xxxxxxxxxxxxxxxx");
fgets(arr, 5, pf);//读5个字符
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}我们也可以改成10个,等等。

观察这个例子, 虽然让它读取10个字符,但遇到了换行符,故打印“\n”和“\0”。
那么如果刚刚好呢?我们读取5个字符,放4个字符,再看看:

这样就没有换行符了(因为还没到换行),就只打印 \0 了。
函数原型:
char* fgets(char* str, int num, FILE* stream);三板斧——
功能:从 stream 指定输入流中读取字符串,至读取到换行符、文件末尾(EOF)或达到指定字符数 (包含结尾的空字符 \0 ),然后将读取到的字符串存储到str指向的空间中。 参数: str :是指向字符数组的指针,str指向的空间⽤于存储读取到的字符串。 num :最大读取字符数(包含结尾的 \0 ,实际最多读取 num-1 个字符)。 stream :输入流的文件指针(如文件流或 stdin )。 返回值: (1)成功时返回str 指针。 (2)若在尝试读取字符时遇到文件末尾,则设置文件结束指示器,并返回 NULL ,需通过feof()函数检测。 (3)若发生读取错误,则设置流错误指示器,并返回NULL,通过 ferror() 检测。
函数原型:
int fscanf ( FILE * stream, const char * format, ... );
//类⽐scanf函数来学习 和scanf的函数原型非常相似,博主之前分享了C语言的网址,这里我就再分享一次:
三板斧——
功能: fscanf 是从指定文件流中读取格式化数据的函数。它类似于scanf ,但可以指定输入源 (如文件、标准输入等),而不是仅限于控制台输入。适用于从文件解析结构化数据(如整数、浮点数、字符串等)。 参数: stream :指向FILE对象的指针,表示要读取的文件流(如stdin 、文件指针等)。 format :格式化字符串,定义如何解析输入数据(如%d 、 %f 、 %s 等)。 ... :可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。 返回值: 1. 成功时,函数返回成功填充到参数列表中的项数。该值可能与预期项数一致,也可能因以下原因少于预期(甚至为零): (1)格式和数据匹配失败; (2)读取发生错误; (3)到达文件末尾(EOF)。 2.如果在成功读取任何数据之前发生: (1)发生读取错误,会在对应流上设置错误指示符,则返回EOF 。 (2)到达文件末尾,会在对应流上设置文件结束指示符,则返回EOF 。
代码演示:
#include<stdio.h>
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s = { 0 };
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf,"%s %d %f", s.name, &(s.age), &(s.score));
printf("%s %d %f\n",s.name,s.age, s.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}函数原型:
int fprintf ( FILE * stream, const char * format, ... );
//类⽐printf函数学习大家可以去这篇文章看看,和printf函数对比一下,链接放在下面哩:
单目操作符++、--的前置后置知识点总结,printf函数的一些知识点
三板斧——
功能:fprintf是将格式化数据写入指定文件流的函数。它与 printf 类似,但可以输出到任意文件(如磁盘文件、标准输出、标准错误等),而不仅限于控制台。 参数: stream :指向FILE对象的指针,表示要写入的文件流(如stdout 、文件指针等)。 format :格式化字符串,包含要写入的文本和格式说明符(如%d 、%s 等)。 ... :可变参数列表,提供与格式字符串中说明符对应的数据。 返回值: (1)成功时,返回写入的字符总数(非负值)。 (2)失败时,先设置对应流的错误指示器,再返回负值,可以通过 ferror() 来检测。
代码演示:
#include<stdio.h>
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s = { "zhangsan",20,85.5f };
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fprintf(pf, "%s %d %f", s.name, s.age, s.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}和printf函数对比一下:
#include<stdio.h>
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s = { 0 };
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf,"%s %d %f", s.name, &(s.age), &(s.score));
printf("%s %d %f\n",s.name,s.age, s.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}因为篇幅的原因,博主会分两篇更新完,上篇内容就到这里,顺序读写还有一些内容我会放在下篇同文件操作剩余的内容一块儿一口气讲完。
往期回顾:
【动态内存管理】深入详解:malloc和free、calloc和realloc、常见的动态内存的错误、柔性数组、总结C/C++中程序内存区域划分
【详解自定义类型:联合和枚举】:联合体类型的声明、特点、大小的计算,枚举类型的声明、优点和使用
【自定义类型:结构体】:类型声明、结构体变量的创建与初始化、内存对齐、传参、位段
结语:本篇文章就到此结束了,本文为友友们分享了文件操作相关的一些重要知识点,如果友友们有补充的话欢迎在评论区留言,下一篇博客,我们将继续介绍文件操作的内容,敬请期待,感谢友友们的关注与支持!