文章目录
- 前言
- 一、文件的打开和关闭
- 打开
- 打开模式
- 相对路径和绝对路径
- 关闭
- 二、文件操作正确流程
- 三、文件顺序读写函数
- fopen
- fclose
- fputc
- fgetc
- fputs
- fgets
- fprintf
- fscanf
- sprintf
- sscanf
- fwrite
- fread
- 四、文件随机读写函数
- fseek
- ftell
- rewind
- 五、文件读取结束时候的判断
- feof
- ferror
- 具体例子
- 总结
前言
承接上文,正文开始!
一、文件的打开和关闭
打开
如果需要对文件进行一些读写操作,那么首先就需要先打开文件,在使用完以后关闭文件,所以最基本是打开文件和关闭文件
ANSIC规定使用fopen函数来打开文件,fclose来关闭文件
打开文件
FILE* fopen(const char* filename, const char* mode);
关闭文件
FILE* fclose(FILE* stream);
打开模式
mode表示文件的打开模式,下面都是文件的打开模式:
文件打开方式 | 含义及如果指定⽂件不存在 |
---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件,出错 |
“w”(只写) | 为了输出数据,打开一个文本文件,建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据,建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 ,出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 ,建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 ,建立一个新的文件 |
“r+”(读写) | 为了读和写,打开一个文本文件,出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 ,建立一个新的文件 |
“a+”(读写) | “a+”(读写),建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件,出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件,建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写,建立一个新的文件 |
使用上面的模式说明符,文件将作为文本文件打开。为了将文件作为二进制文件打开,模式字符串中必须包含“b”字符。这个附加的“b”字符可以附加在字符串的末尾(从而形成以下复合模式:“rb”、“wb”、“ab”、“r+b”、“w+b”、“a+b”),也可以插入在“+”符号之前(“rb+”、“wb+”、“ab+”)
在某些库实现中,使用更新模式打开或创建文本文件可能会视为二进制文件
来举几个例子:
一、我们要以文本形式打开一个名叫data.txt的文件,将要对其进行输入操作:
//注:data.txt文件必须存在,不然打开文件失败,fopen函数会返回一个空指针
FILE* pf = fopen("data.txt", "r");
二、若我们要以二进制打开一个名叫data.bin的文件,将要对其进行输出操作:
//注:data.bin文件若存在,将销毁文件原有内容,再对其进行输出;data.bin文件若不存在,系统将主动创建一个名叫data.bin的文件
FILE* pf = fopen("data.bin", "wb");
所以我们一般要检测一下文件指针的有效性:
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//失败返回
}
//使用...
相对路径和绝对路径
填写fopen函数的第一个参数的时候,如果我们要打开的文件与我们正在运行的源代码在同级目录下(以打开data.txt文件为例),那么我们应该这样写:
FILE* pf = fopen("data.txt", "r");//同级
但是如果我们想要打开的文件与当前运行的源代码不在同一级目录下呢?这就涉及到相对路径和绝对路径了~
- 当待打开的文件位于正在运行的源代码的上一级时:
FILE* pf = fopen("../data.txt", "r");//上一级
- 当待打开的文件位于正在运行的源代码的下一级时:
//注:这里data.txt文件在Debug文件内,Debug文件与正在运行的源代码在同级目录下
FILE* pf = fopen("Debug/data.txt", "r");//下一级
而有人可能觉得相对路径的方法比较麻烦(可能还要去数目标文件与源文件相差的级数),绝对路径就没那么麻烦了,绝对路径就直接写出目标文件的完整路径即可:
//注:文件的路径原本为"D:\code\File_function\File_function\data.txt",
//但是为了防止字符串中的’\‘及其后面的字符被整体视为为转义字符,所以需要在每个’\‘后面再加一个’\’
FILE* pf = fopen("D:\\code\\File_function\\File_function\\data.txt", "r");
关闭
与动态开辟内存空间时一样,当我们打开文件时,会在内存中开辟一块空间,如果我们打开该文件后不关闭,那么这个空间会一直存在,一直占用那块内存空间,所以当我们对一个文件的操作结束时,一定要记住将该文件关闭。这就需要用到fclose函数来关闭文件:
int fclose(FILE *stream);
我们如果要关闭一个文件,那么直接将该文件的文件指针传入fclose函数即可,fclose函数如果关闭文件成功会返回0。与free函数一样,当我们调用完fclose函数将文件关闭后,我们也要将指向文件信息区的指针置空,避免该指针变成野指针
fclose(pf);//关闭pf指向的文件
pf = NULL;//及时置空
二、文件操作正确流程
知道了如何打开文件和关闭文件,也就相当于知道了如何进入和如何出去,所以当我们要对一个文件进行操作的时候,正确的流程应该是这样的:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)//检测指针有效性
{
printf("%s\n", strerror(errno));//错误提醒
return 1;//失败返回
}
//对文件进行一系列操作
//...
//关闭文件
fclose(pf); //关闭pf指向的文件
pf = NULL;//及时置空
return 0;
}
三、文件顺序读写函数
fopen
FILE *fopen(const char * filename, const char * mode)
功能:打开其名称在参数 filename 中指定的文件,并将其与流相关联,该流可在将来的操作中通过返回的 FILE指针进行标识
返回值:如果文件成功打开,该函数将返回指FILE对象的指针,该对象可用于在将来的操作中标识流,如果文件没有成功打开,则将返回NULL
允许对流执行的操作以及如何执行这些操作由 mode 参数定义
fclose
int fclose(FLIE *stream)
功能:关闭与流关联的文件并取消关联
与流关联的所有内部缓冲区都将与其解除关联并刷新:写入任何未写入的输出缓冲区的内容,并丢弃任何未写入输入缓冲区的内容。(更新缓冲区)
返回值:流关闭成功,则返回0。关闭失败,返回EOF
fputc
功能:将字符写入流并推进位置指示器,然后自动前进1。适用于所有输入流(字符输入到流中)
参数部分:character->将要写入的字符的整型提升,写入时,该值在内部转换为无符号字符;stream->指向标识输出流的FILE对象指针
返回值:成功,将返回写入的字符。发生写入错误,则返回EOF并设置错误指示符(ferror)
int main()
{
FILE* pf = fopen("test1.txt", "w");
if (pf == NULL)
{
perror("fopen fail!!!");
return 1;
}
fputc('d', pf);
fputc('e', pf);
fclose(pf);
pf = NULL;
return 0;
}
请注意,对于stream可以是文件流也可以是标准流
那么现在,使用fputc将A ~Z个字符输入到指定文件和显示器中,该怎么办?
fgetc
功能:字符输入函数,从流中获得字符,返回指定流的内部文件位置指示符当前指向的字符,内部文件位置指示器将前进到下一个字符
返回值:成功后,将返回字符读取(进行整型提升)。如果失败则,返回EOF
int main()
{
FILE* pf = fopen("test1.txt", "r");
if (pf == NULL)
{
perror("fopen fail!!!");
return;
}
char ch;
//ch用于接收fgetc从流中获得的字符
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
那么现在,我想进行文档的拷贝,这又该怎么办?
fputs
功能:文本行输入函数,将str指向的字符串写入流中,从指定的地址开始复制,直到结束标志\0(\0’字符不会复制到流中)
返回值:成功,返回一个非负值,失败,则返回EOF
int main()
{
FILE * pf = fopen("test1.txt", "w");
if (pf == NULL)
{
perror("fopen fail!!!");
return;
}
fputs("hellow world", pf); //将字符串输入到文件流中
fputs("hellow world", stdout); //将字符串输入到输出流中(打印到屏幕上)
fclose(pf);
pf = NULL;
return 0;
}
fgets
功能:文本行输入函数,从流中获得字符串,直到读取到num-1个字符或者在达到换行符或文件末尾(前者为准)。换行符(\n)会使fgets停止,但函数将其视为有效字符,并包含在复制到str的字符串中,在将字符复制到str之后,会自动添加\0,导致只能读取num-1字符
参数部分上:str->指向复制字符串读取到char数组的指针(这里提前开辟好数组)、num->复制到str中的最大字符数(包括‘\0’字符-字符串)、返回值->成功,返回str指针,失败,返回EOF
返回值:成功,返回str指针,失败,返回EOF
int main()
{
FILE* pf = fopen("test1.txt", "r");
if (pf == NULL)
{
perror("fopen fail!!!");
return;
}
char str[1000]="xxxxxxx";
fgets(str, 7, pf);
printf("%s", str);
fclose(pf);
pf = NULL;
return 0;
}
fprintf
功能:按格式化数据写入流,如果format包含格式说明符(%开头的子序列),则格式化format后面的其他参数并将其插入到生成的字符串中,代替其各自的说明符,根据格式说明符顺序,匹配不同类型对应的数据,输入到text1.txt中去
返回值:成功,返回写入字符总数,失败,则返回负数
struct S
{
char str[1000];
int i;
float f;
};
int main()
{
struct S s={"zhangsan", 100, 4.3};
FILE* pf = fopen("test1.txt", "w");
if (pf == NULL)
{
perror("fopen fail!!!");
return 1;
}
fprintf(pf,"%s %d %f", s.str, s.i, s.f);
fclose(pf);
pf = NULL;
return 0;
}
fscanf
功能:从流中读取数据,并根据格式将其存储到其他参数指向的位置,该对象由格式化字符串的相应格式说明符指示,根据格式说明符顺序,匹配text1.txt对应的数据,输入到对应的数据变量中
返回值:成功后,该函数返回已成功填充的参数列表的项数,失败,EOF
struct S
{
char str[1000];
int i;
float f;
};
int main()
{
struct S s = {0};
FILE* pf = fopen("test1.txt", "r");
if (pf == NULL)
{
perror("fopen fail!!!");
return 1;
}
fscanf(pf, "%s %d %f", s.a, &(s.p), &(s.pa));
printf("%s %d %f", s.a, s.p, s.pa);
fclose(pf);
pf = NULL;
return 0;
}
sprintf
功能:从字符串中读取格式化数据(将字符数组中的字符串按照格式说明符转换为对应的格式化数据)
sscanf
功能:将格式化数据写入字符串(格式化数据转换为字符串存放在字符数组中)
// sprintf和sscanf的实际运用
int main()
{
//将格式化的数据转换为字符串存放在p数组中
struct S s = { "zhangsan",100,4.3 };
char p[1000] = { 0 };
sprintf(p, "%s %d %f", s.a, s.p, s.pa);
printf("%s\n", p);
//从p这个字符串中提取格式化的数据(用p数组中的数据,为结构体t成员赋值)
struct S t = {0};
sscanf(p, "%s %d %f", t.a, &(t.p), &(t.pa));
printf("%s %d %lf", s.a, s.p, s.pa);
return 0;
}
fwrite
功能:二进制输出,将 count 元素数组(每个元素的大小为 size bytes)从 ptr 指向的内存块写入流中的当前位置
返回值:成功将返回写入元素个数,如果此数字与 count 参数不同,则写入错误会阻止函数完成,如果 size 或 count 为零,则函数返回零,错误指示器保持不变
int main()
{
FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
if (pf == NULL)
{
perror("fopen fail!!!");
return;
}
int nums[] = { 1,2,3,4,5,6,7 };
fwrite(nums, sizeof(int), 10, pf);
fclose(pf);
pf = NULL;
return 0;
}
请注意,这里是通过二进制的形式写入文本文件中,如果想要看懂输入的数据,可以在打开方式选择二进制编辑器或者使用fread函数读取出来
fread
功能:二进制输入,从流中读取 count 元素的数组,每个元素的大小为 bytes,并将它们存储在 ptr 指定的内存块中
返回值:成功将返回写入实际元素个数,如果此数字与 count 参数不同,则表示读取时发生读取错误或达到文件末尾,如果 size 或 count 为零,则该函数返回零, ptr 指向的流状态和内容保持不变
如果实际文件中只有7个字符,count是14,那么读到7给字符就结束(返回值 < count就返回)
int main()
{
FILE* pf = fopen("test1.txt", "rb");//打开一个二级制文件
if (pf == NULL)
{
perror("fopen fail!!!");
return;
}
//int nums[] = { 1,2,3,4,5,6,7 };
//fwrite(nums, sizeof(int), 7, pf);
//读取二进制存放的信息
int nums[10] = { 0 };
fread(nums, sizeof(int), 10, pf);
for (int i = 0; i < 10; i++)
{
printf("%d ", nums[i]);
}
fclose(pf);
pf = NULL;
return 0;
}
四、文件随机读写函数
fseek
功能:重新定位流位置指示器,根据文件指针的位置和偏移量来定位文件指针
返回值:成功,返回零,失败,返回非零值
注意,对于二进制文件:要从原点偏移的字节数,对于文本文件:零或 ftell 返回的值
对于参数origin
- SEEK_SET :文件开头
- SEEK_CUR :文件指针的当前位置
- SEEK_END: 文件末尾
int main()
{
FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
if (pf == NULL)
{
perror("fopen fail!!!");
return 1;
}
fputs("This is an apple", pf);
fseek(pf, 3, SEEK_SET);
fputs("sam", pf);
fseek(pf, -3, SEEK_END);//文件指针往前走
fputs("xxx", pf);
fclose(pf);
pf = NULL;
return 0;
}
ftell
功能:返回流的位置指示器的当前值(返回文件指针相对于起始位置的偏移量 )
返回值:对于二进制流,这是从文件开头开始的字节,对于文本流,数值可能没有意义
int main()
{
FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
if (pf == NULL)
{
perror("fopen fail!!!");
return 1;
}
fputs("This is an apple", pf);
fseek(pf, 0, SEEK_END);
int size = ftell(pf);
printf("%d", size); //size == 16,源头到参考位置的字节数
fclose(pf);
pf = NULL;
return 0;
}
rewind
功能:让文件指针的位置回到文件的起始位置
int main()
{
FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
if (pf == NULL)
{
perror("fopen fail!!!");
return 1;
}
fputs("This is an apple", pf);
fseek(pf, 0, SEEK_END);
rewind(pf);
int size = ftell(pf);
printf("%d", size);
fclose(pf);
pf = NULL;
return 0;
}
五、文件读取结束时候的判断
feof
功能:在文件读取结束后,判断是否是因为遇到文件末尾而结束,即看返回值是否是EOF
ferror
功能:在文件读取结束后,判断是否是因为遇到错误而结束
具体例子
牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件的是否结束。
- 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:fgetc 判断是否为 EOF ,fgets 判断返回值是否为NULL - 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
例如:fread判断返回值是否小于实际要读的个数
//文本文件
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if (!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully"); // 输出这个
fclose(fp);
fp = NULL;
return 0;
}
//二进制文件
#include <stdio.h>
#include <stdlib.h>
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
if (ret_code == SIZE) {
puts("Array read successfully, contents: "); // 输出这个
for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
putchar('\n');
}
else { // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
fp = NULL;
return 0;
}
总结
文件操作,这篇我写得应该算是不太好,暂且将就一下吧~