前言
在日常应用中,我们为了持续的使用一些数据,为了让数据可以在程序退出后可以保存并正常使用,引入了文件的概念和操作。本文分享了一些常用的文件操作函数的使用方法和各自的区别。
一、常用文件顺序读写函数
下面例程所使用的VS工程代码见:👇
fgetc fputc fgets fputs fscanf fprintf fread fwrite文件操作函数使用例程代码
功能 | 函数名 | 用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
二、字符操作函数 fgetc 和 fputc
fgetc
c在这里指的是char
官方解释如上图
- 从流中获取字符
- 返回指定流的内部文件位置指示符当前指向的字符。然后将内部文件位置指示符推进到下一个字符
- 如果流在被调用时位于文件的末尾,则该函数返回 EOF 并为流设置文件结束指示器(feof)
- 如果发生读错误,该函数返回EOF并设置流的错误指示器(error)
- Fgetc 和 getc 是等价的,除了 getc 可以在某些库中作为宏实现
为此我们写了个文件
在确保了文件可以有内容读取后,我们编程实现观察结果,我们以读取的方式打开文件,然后使用 ch 变量挨个读取文件中的字符并且打印。
int char_operation1(void)
{
FILE* pf = fopen("data1.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int 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;
}
调用此函数并获取结果如下
- 综上:fgetc 函数可以挨个读取文件中的字符,我们可以定义一个变量来接收 fgets 的返回值(字符的ASCII码)然后进行输出等操作
- 在使用该函数的时候,我们只需要将文件指针(流)作为参数传给函数,然后我们就可以得到一个字符的ASCII码
fputc
官方解释如上图
- 将字符写入流
- 将一个字符写入流并推进位置指示器
- 字符被写入流的内部位置指示器所指示的位置,然后自动向前移动一个
在实验前创建一个空白的文件
int char_operation2(void)
{
//打开文件
FILE* pf = fopen("data2.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
fputc('d', pf);
fputc('e', pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行后:
通过 fputc 函数挨个对文件输入了从 ‘a’ 到 ‘e’ 的字符
综合上述操作,我们可以得出结论,fputc 函数可以挨个字符的对文件进行写入的操作,函数内部需要注意的有俩个参数
- 要写入的字符
- 要写入文件的文件指针(流)
三、文本行操作函数fgets 和 fputs
fgets
s指的是string
- 从流中获取字符串
- 从流中读取字符,并将其作为C字符串存储到 str 中,直到读取 (num-1) 个字符,或者到达换行符或文件结束符,以先发生的为准
- 换行符使 fgets 停止读取,但它被函数认为是一个有效字符,并包含在复制到 str 的字符串中。
- 在复制到 str 的字符之后,将自动追加一个终止 null 字符。
- 请注意,fgets 与 gets 有很大的不同:fgets 不仅接受流参数,而且允许指定 str 的最大长度,并在字符串中包含任何结束换行符。
提前准备一个txt文件
int string_operation1(void)
{
//打开文件
FILE* pf = fopen("data3.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[100] = { 0 };
fgets(arr, 100, pf);
printf("%s", arr);
fgets(arr, 100, pf);
printf("%s", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果如下:
根据输出结果,我们可以发现,一条 fgets 函数可以读取一行的内容,但是在使用的时候需要注意设置一个字符数组来接收字符串,函数在使用时,一共有三个参数
- 要传入数组的地址
- 要复制到字符串中的最大字符数
- 文件指针(流)
fputs
- 将字符串写入流
- 将由 str 指向的C字符串写入流
- 函数从指定的地址 (str) 开始复制,直到到达结束的空字符 ('\0'),这个终止的空字符不会复制到流中
- 注意,fputs 与 puts 的不同之处不仅在于可以指定目标流,而且 fputs 不会写入额外的字符,而 puts 会自动在末尾附加一个换行符
int string_operation2(void)
{
//打开文件
FILE* pf = fopen("data4.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
char arr[] = "hello world\n";
fputs(arr, pf);
fputs("Deadly Bug", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
首先还是设定一个空白内容的 data.txt 的文件
运行结果如下
根据运行结果,我们可以判断, fputs 函数一次性可以对文件写入一行字符串,只要声明字符串的内容或者地址,都可以进行写入,它的俩个参数分别如下:
- 要写入的字符串的内容或者地址
- 要写入的文件指针(流)
四、格式化文件操作函数fscanf 和 fprintf
fscanf
- 从流中读取格式化的数据
- 从流中读取数据,并根据参数格式将其存储到附加参数所指向的位置
- 额外的参数应该指向已经分配的对象,其类型由格式字符串中相应的格式说明符指定
需要以特定的格式的字符串
int format_operation1(void)
{
struct S s = { 0 };
//打开文件
FILE* pf = fopen("data5.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf, "%f %c %d", &(s.f), &(s.c), &(s.n));
printf("%f %c %d\n", s.f, s.c, s.n);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
注:格式应与文件中一样
根据输出结果,我们可以发现,原本文件中的字符串,被我们以 float char int 的格式分别读到了 f,c,n 三个变量中,也就是说我们将原本的字符串变成了不同格式的数据,在使用这个函数时,注意他的参数,只比 scanf 多了一个参数,也就是文件指针(流)
fprintf
- 将格式化的数据写入流
- 将由 format 指向的 C字符串写入流。如果 format 包含格式说明符(以%开头的子序列),则格式化format 之后的其他参数并将其插入到结果字符串中,以替换它们各自的说明符。
- 在format 形参之后,函数期望至少与format 指定的一样多的附加参数。
int format_operation2(void)
{
struct S s = { 1.2345f, 'a', 100 };
//打开文件
FILE* pf = fopen("data6.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fprintf(pf, "%f-%c-%d", s.f, s.c, s.n);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果如下:
根据输出结果,我们可以发现,我们以结构体的格式,将其中 float char int 三种类型的数据写入了文件,在写入文件后,这些数据就失去了原本的格式,相当于变成了一条字符串,在使用的时候,他只比我们的 scanf 多了一个参数,也就是文件指针(流)
五、二进制文件操作函数
fread
- 从流中读取数据块
- 从流中读取一个由 count 元素组成的数组,每个元素的大小为 size 字节,并将它们存储在 ptr 指定的内存块中
- 流的位置指示器按读取的总字节数前进
- 如果成功读取的总字节数为 (size*count)
参数
-
void * ptr:指向大小至少为(size*count)字节的内存块的指针,转换为 void*
-
size_t size:要读取的每个元素的大小(以字节为单位)
-
size_t count:元素的数目,每个元素的大小为 size 字节
-
FILE * stream:指向指定输入流的 FILE 对象的指针(文件指针)
首先有个txt文件
int binary_operation1()
{
//二进制的方式读取文件
int arr[15] = { 0 };
//写文件
FILE* pf = fopen("data7.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//二进制的读文件
fread(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
int i = 0;
for (i = 0; i < 15; i++)
{
printf("%d ", arr[i]);//以二进制文件读取的所以是乱数
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果如下
结果和预期的完全不一样,这是因为是以二进制的方式读取,打印的时候却是以十进制的方式打印,这样就相当于给数据加密了,我们无法直观的看见我们读取了什么数据,但是我们确确实实是读取到了。那么如何读取到呢?下面会做出介绍
fwrite
- 将数据块写入流
- 将由 count 元素组成的数组 (每个元素的大小为 size 字节) 从 ptr 所指向的内存块写入流中的当前位置
- 流的位置指示器按写入的总字节数前进
- 在内部,该函数将 ptr 指向的块解释为 unsigned char 类型的 (size*count) 元素数组,并将它们顺序写入流,就像对每个字节调用 fputc 一样
参数:
-
void * ptr:指向大小至少为(size*count)字节的内存块的指针,转换为 void*
-
size_t size:要写入的每个元素的大小(以字节为单位)
-
size_t count:元素的数目,每个元素的大小为 size 字节
-
FILE * stream:指向指定输入流的 FILE 对象的指针(文件指针)
int binary_operation2()
{
//二进制的方式写进文件
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//写文件
FILE* pf = fopen("data8.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//二进制的写文件
fwrite(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果如下:
同样,我们还是无法直观的看见我们存储了什么,这是因为记事本使用的是 UTF-8 码,和我们保存的二进制并不兼容,这从十进制转到二进制再用 UTF-8 码进行显示也就相当于对文本进行了一层加密,解铃还须系铃人,通过fread读取试一下
int binary_operation3()
{
//二进制的方式读取文件
int arr[10] = { 0 };
//写文件
FILE* pf = fopen("data8.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//二进制的读文件
fread(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果如下
这一次可以正确读取到数据了,也就是说fread 和 fwrite都不会影响数据的真实性,只是被加密了,无法直观看到。
总结:
fgetc:从文件中读取单个字符(char)
fputc:对文件写入单个字符(char)
fgets:从文件中读取一个字符串(string)
fputs:对文件写入一个字符串(string)
fscanf:将文件中的字符串转换为有格式的数据并且读取出来(format)
fprintf:将有格式的数据转换为字符串并且写入文件(format)
fread:将文件中的内容转换为二进制然后读取出来(binary)
fwrite:将数据转换为二进制然后写入文件(binary)