前言:在上一篇博客关于文件的基础知识中我们对文件有了一定的了解,本篇博客将会详细讲解操作文件的函数。
文章目录
文件的顺序读写
文件顺序读写的函数如下,接下来我们将一一介绍这些函数。
注:在使用这些函数之前要引用头文件 <stdio.h>
在此之前,我们需要知道关于流 (stream) 的一些概念:
流是一个高度抽象的概念,如图:
即程序向流这个层次中输入数据,流再把相应的数据写到不同的设备上(至于如何写的,目前不用我们关心这些细节);例如:当我们打开文件,在写文件时,就可以理解成我们在向一个文件流里写入数据。
另外需要注意,对于C语言程序,只要运行起来,就默认打开了3个流:
stdin——标准输入流(对应键盘)
stdout——标准输出流(对应屏幕)
stderr——标准错误流(对应屏幕)
这3个流的类型都为 FILE* 型的流的文件指针。
字符输入输出函数 —— fgetc 与 fputc
fputc函数
函数的功能为向一个流中写入字符,该函数的返回类型为 int 型,对于返回值,有如下描述:
即当函数写入字符成功,则返回字符相应的ASCII码值,当写入失败,则返回EOF。
例如:
将字符‘a’、‘b’、‘c’写入到标准输出流
通过函数将字符写入到屏幕。
再例如:
#include<stdio.h>
#include<string.h>
int main()
{
//创建并打开文件
FILE* pf = fopen("test.txt", "w");//以读的方式打开文件
//注:若文件中原本就有test.txt文件,则不用再创建了,若没有,则将会先创建该文件后再以相应的方式打开文件
if (pf == NULL)
{
perror("fopen");
return 1;
}
int i = 0;
for (i = 'a'; i <= 'z'; i++)
{
fputc(i, pf);//运用循环将字符a~z写入到文件流中
}
fclose(pf);//关闭文件
pf = NULL;
return 0;
}
当程序运行完成后,打开文件路径就可以看到我们在程序中创建的文件,用记事本打开后就能看见文件中的内容,如下:
fgetc函数
该函数的功能为从流中读取一个字符,返回类型如fputc一样(不多说明)
例如:
首先在相应的文件路径下创建一个文件如下:
#include<stdio.h>
#include<string.h>
int main()
{
FILE* pf = fopen("test1.txt", "r");//以读的方式打开文件
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ret=fgetc(pf);//从标准输入流中获取字符
printf("%c\n", ret);//打印该字符,下同
ret = fgetc(pf);
printf("%c\n", ret);
ret = fgetc(pf);
printf("%c\n", ret);
fclose(pf);
pf = NULL;
return 0;
}
通过fgetc函数就可从文件流中获取字符,然后通过打印在屏幕上显示。
再例如:
即先在屏幕上输入字符,然后函数在从屏幕上获取字符。
需要注意的是,无论是从屏幕上还是从文件中获取字符,当函数运行一次后,流(标准输入流或者文件流)的指针就会向后移动一步,所以函数运行一次字符就会依次打印出来。
文本行输入输出函数 —— fgets 与 fputs
fputs函数
该函数的功能是将一个字符串写入到一个流中,函数的第一个参数为被写入的字符串(首地址),第二个参数为一个流,函数的返回值为 int 型,当函数写入成功,则返回一个正数,当写入失败,则返回EOF。
例如:
#include<stdio.h>
#include<string.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[] = "Hello world!";
fputs(arr, pf);
fclose(pf);
pf = NULL;
return 0;
}
可见该文件打开成功且字符串写入成功!
当要写入字符串到屏幕上时如下(类似于fputc的运用):
fgets函数
该函数的功能为从流中读取一个字符串到相应的存储位置;第一个参数为获取到字符串的存储的位置,第二个参数为要读取的字符串的个数(也可称为要读取的字节数),第三个参数为要读取文件的文件指针(文件流),若函数读取成功,则返回储存字符串位置的地址,如果读取过程中发生错误,或是读取到了文件末尾,则返回一个空指针(NULL) 。
如下:
首先创建一个文件
#include<stdio.h>
#include<string.h>
int main()
{
FILE* pf = fopen("data1.txt", "r");//以读的方式打开文件
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[20] = { 0 };
fgets(arr, 5, pf);//将文件中的字符串读入到arr中
//注:这里读取的为5个字节,其中包含字符'\0'
printf("%s\n", arr);//打印出读取后的字符串
fclose(pf);
pf = NULL;
return 0;
}
当要从屏幕上读取(即标准输入流)则类似于fgetc,如下:
格式化输入输出函数 —— fscanf 与 fprintf
fprintf函数
该函数的功能为将格式化的数据写入到目标流中,函数的第一个参数为一个文件流,即数据写入的目标流,第二个参数看似比较复杂,但是没关系,我们可以以另一个函数做类比,即将该函数以printf函数的参数做对比,如下:
我们对 printf 函数并不陌生,所以我们在这里可以类比出 fprintf 的参数形式。
举个栗子:
我们要将一个结构体类型的变量信息输出到data.txt文件中去
#include<stdio.h>
#include<string.h>
struct S
{
char name[20];
int age;
char sex[5];
};
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
struct S s = { "张三",18,"男" };
fprintf(pf, "%s %d %s", s.name, s.age, s.sex);
fclose(pf);
pf = NULL;
return 0;
}
可见该结构体写入文件成功。
所以我们可见 fprintf 函数的功能就是将 “区域一”中的数据以 “区域二” 的格式写入到 “区域一”,如下:
fscanf函数
当我们熟悉 fprintf 函数后就会很容易理解 fscanf 函数,在与scanf函数进行对比如下,我们可知该函数的第二个参数与 scanf 函数的参数一样。
举个栗子:
我们首先创建一个文档
#include<stdio.h>
#include<string.h>
struct S
{
char name[20];
int age;
char sex[5];
};
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
struct S s = { 0 };
fscanf(pf, "%s %d %s", s.name, &s.age, s.sex);
printf("%s %d %s\n", s.name, s.age, s.sex);
fclose(pf);
pf = NULL;
return 0;
}
可见通过fscanf函数能够读取文件中的数据,所以我们可以知道:
fscanf函数的功能就是将 “区域一” 中的数据以 “区域二” 的格式读取到 “区域三” 中。
扩展(函数对比)
对比以下两组函数:
scanf / fscanf / sscanf
printf / fprintf / sprintf
这里我们首先讲解一下 sscanf 函数与 sprintf 函数。
对于sprintf函数,原型如下:
根据文档解释我们可知该函数能够将一个格式化的数据以字符串的形式写入到目标地址 buffer 中,该函数的第一个参数为转换为字符串的目标地址,第二个参数参考 fprintf 或 printf 函数(三者的此参数一样)。
栗子如下:
struct S
{
char name[20];
int age;
char sex[5];
};
int main()
{
struct S s = { "张三",18,"男" };
char buf[100] = { 0 };
sprintf(buf, "%s %d %s", s.name, s.age, s.sex);//将结构体s中的数据以字符串的形式写入到buf中
printf("%s\n", buf);
return 0;
}
可见结果为将结构体 s 中的数据以字符串的形式写入到 buf 中。
对于sscanf函数,原型如下:
该函数的功能是能够从字符串 ( buffer ) 中读取格式化的数据,第一个参数为被读取字符串的地址,第二个参数参考fscanf或scanf函数。
应用如下:
struct S
{
char name[20];
int age;
char sex[5];
};
int main()
{
struct S s = { "张三",18,"男" };
char buf[100] = { 0 };
sprintf(buf, "%s %d %s", s.name, s.age, s.sex);
printf("%s\n", buf);
printf("\n");
struct S s1 = { 0 };
sscanf(buf, "%s %d %s", s1.name, &(s1.age), s1.sex);//将字符串buf中的数据读取到结构体s1中
printf("%s %d %s", s1.name, s1.age, s1.sex);
return 0;
}
总结:
scanf——针对标准输入(stdin)的格式化输入语句
fscanf——针对所有输入流(stdin/文件)的格式化输入语句
sscanf——从一个字符串中读取一个格式化的数据
printf——针对标准输出(stdout)的格式化输出语句
fprintf——针对所有输出流(stdout/文件)的格式化输出语句
sprintf——把一个格式化的数据,转换为字符串
二进制输入输出函数 —— fread与 fwrite
fwrite函数
函数的原型为:
对于参数,文档给出如下解释;
即第一个参数为一个指针,即函数输入数据的位置,第二个参数为字节数,第三个参数为要写入的元素个数,第四个参数为数据写入的目标位置,即目标流。
所以我们可以看出该函数的功能就是能将最多 count 个 size 字节大小的 buffer 地址的数据写入到目标 stream 中。
例如
#include<stdio.h>
#include<string.h>
struct S
{
char arr[20];
int x;
float y;
};
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
struct S s = {"abcdef",12,5.5f};
fwrite(&s, sizeof(struct S), 1, pf);//写入数据到结构体s中
fclose(pf);
pf = NULL;
return 0;
}
可见,此时的文件中是有内容的,但是电脑是以二进制的形式写入的,只是我们看不懂。
fread函数
函数原型:
该函数也是4个参数,参数的意义与 fwrite 函数的参数类似,不同的是该函数的功能是从 stream 中获取最多 count 个 size 字节大小的数据到 buffer 地址中。
例如:
我们在fwrite函数的讲解中已经在data.txt文件中写入数据,接下来我们再将data.txt中的数据读读取出来并打印在屏幕上
struct S
{
char arr[20];
int x;
float y;
};
int main()
{
struct S s = { 0 };
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(&s, sizeof(struct S), 1, pf);//从pf中读取数据到s中
printf("%s %d %f\n", s.arr, s.x, s.y);
fclose(pf);
pf = NULL;
return 0;
}
可见函数从文件中以二进制的形式读取成功。
文件的随机读写
fseek函数
该函数原型:
函数的功能为将含函数指针移动到一个特定的位置;该函数的第一个参数是要移动位置的文件指针,第二个参数是文件指针经操作后相对于这个“起始位置”的偏移量,第三个参数是“初始位置”(并非文件信息区的起始位置),单位为字节。对于返回值,fseek函数如果调用成功,则返回0;若调用失败,则返回一个非0的值。
对于该函数的第三个参数 origin 有如下三种形式:
参数形式 | 表示意义 |
---|---|
SEEK_CUR | 文件指针的当前位置 |
SEEK_SET | 文件开头 |
SEEK_END | 文件末尾 |
应用举例:
我们先在data.txt 文件中写入“Hello world!”
为了使文件指针移动三次我们这样操作:
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int i = 0;
for (i = 0; i < 7; i++)
{
fgetc(pf);
}
fclose(pf);
pf = NULL;
return 0;
}
此时文件指针pf指向了字符串中的字符 ‘w’ 。
如果我们要读取 data.txt 文件中的字符 ‘r’ ,则有三种方式:
1.让文件指针相对于其当前位置向后偏移2个字节
fseek(pf, 2, SEEK_CUR);//调整文件指针位置,即从字符‘w’的位置向后偏移2个单位,文件指针就指向了字符‘r’
2.让文件指针相对于文件开头偏移9个字节(即指向文件开头)
fseek(pf, 9, SEEK_CUR);
3.让文件指针相对于文件末尾向前偏移3个字节
fseek(pf, -3, SEEK_END);
经过这三种方法均可使文件指针指向特定的位置。
ftell函数
为了更好地明确文件指针位于什么位置,于是出现了ftell函数,它用于计算当前文件指针相对于起始位置的偏移量。函数原型如下:
函数的参数就为文件指针,当函数运行成功时,返回文件指针文件指针相对于文件起始位置的偏移量,若运行失败,则返回 -1 。
例如:
上述代码首先利用fseek函数使文件指针从起始位置向后偏移5个单位,在利用ftell函数求出函数的偏移量为5。
rewind函数
函数原型为:
该函数的功能为使文件指针 stream 返回到起始位置。
例如:
当文件向后偏移,然后在利用rewind函数使指针指向起始位置,利用fgetc函数打印出第一个字符。
文件读取结束的判定
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1 . 文本文件读取是否结束,判断返回值是是否为 EOF( fgetc ),或者 NULL( fgets )。
例如:
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL
正确的使用:
文本文件的例子如下
int main(void)
{
int c;//注意是int,非char,要求处理EOF
FILE*fp=fopen("text.txt","r");
if(!fp){
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc当读取失败或者遇见文件结束的时候,都会返回EOF
while((c=fgetc(fp))!=EOF)
{
putchar(c);
}
//判断是什么原因结束的
if(ferror(fp))
puts("读取失败");
else if(feof(fp))
puts("遇见文件结尾了");
fclose(fp);
}
对于文件操作的详解到此结束!