C语言笔记第15篇:文件操作

1、为什么使用文件?

如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。

2、什么是文件?

磁盘(硬盘)上的文件就是文件。

但是程序设计中,我们一般谈两个文件,分别是程序文件、数据文件(从文件的角度来分类的)。

2.1 程序文件

程序文件包括源程序文件(后缀为.c)、目标文件(windows环境后缀为.obj),可执行文件(windows环境后缀为.exe)。

2.2 数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

本章讨论的是数据文件。

在以前各篇笔记所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上,其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。

2.3 文件名

一个文件要有唯一的文件表示,以便用户识别和引用。

文件名包含3部分:文件路径+文件主干+文件后缀

例如:c:\code\test.txt

为了方便起见,文件标识常被称为文件名

3、二进制文件和文本文件

根据数据的组织形式,数据文件被称为文本文件或者二进制文件

数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件

如果要求在外出上以ASCII的形式存储,则需要再存储前转换,以ASCII字符的形式存储的文件就是文本文件

一个数据在文件中是怎么存储的呢?

字符一律以ASCII形式存储,数值型数据既可以用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;
}

4、文件的打开和关闭

4.1 流和标准流
4.1.1

程序的数据是要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。C程序真的文件、画面、键盘灯的数据输入输出操作都是通过流操作的。

一般情况下,我们要想向流里写数据,或者从流里读数据,都是要打开流,然后操作。

4.1.2 标准流

文件操作时我们需要自己打开文件(流),当操作完后需要自己关闭文件(流),那为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语言程序在启动的时候,默认打开了3个流:

  • stdin - 标准输入流,大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
  • stdout - 标准输出流,大多数环境中输出值显示器界面,printf函数就是将信息输出到标准输出流中。
  • stderr - 标准错误流,大多数环境中输出到显示器界面。

这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输入输出操作的。

stdin、stdout、stderr 三个流的类型是:FILE*,通常称为文件指针

C语言中,就是通过FILE*的文件指针来维护流的各种操作的。

4.2 文件指针

缓冲文件系统中,关键的概念是 "文件类型指针" ,简称为 "文件指针"。

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名FILE

例如:VS2013编译环境提供的stdio.h头文件中有以下的文件类型声明:

struct _iobuf{char *_ptr;int _cnt;char* _base;int _flag;int _file;int _charbuf;int _bufsiz;char* tmpfname;
};
typedef struct _ioduf FILE;

不同的c编译器的FILE类型包含的内容不完全相同,但是大同小异。

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE类型的变量,并填充其中信息,该结构体类型的变量里存放着我们需要打开的文件的信息,因此被称为文件信息区。使用时不必关心细节。开辟好文件信息区后便会返回该信息区的地址,我们需要FILE*类型的指针来接收这个地址,这个FILE*类型指针就是流,属于文件的流。

一般都是通过FILE指针来维护这个FILE结构变量,这样使用更加方便。

FILE* PF;//文件指针变量

定义pf是一个指向FILE类型的指针变量,可以使pf指向某个文件的文件信息区(是一个结构体变量),通过该文件信息区中的信息就能够访问该文件,也就是说,通过文件指针变量能够间接找到与它关联的文件

比如:

4.3 文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。

在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。

ANSI C规定使用fopen来打开文件,fclose来关闭文件。

//打开文件
FILE* fopen(const char* filename, const char* mode);//关闭文件
int fclose(FILE* ftream);

fopen的函数声明:参数1:filename是所需的文件名,参数2:mode是打开流的形式,是输入还是输出。返回类型:FILE*是一个文件信息区的地址,通过该地址找到文件信息区访问文件。

fclose的函数声明:参数:ftream是我们打开文件时用来接收fopen返回值是创建的变量,将这个变量所存储的地址传参过去就可以回收文件信息区所占用的空间,就是关闭文件

fopen函数的参数2mode的打开形式是什么意思呢?怎么表示打开形式呢?

mode表示文件的打开模式,下面都是文件的打开模式:

文件使用方式含义如果指定文件不存在
"r"(只读)为了输入数据,打开一个已经存在的文本文件出错
"w"(只写)为了输出数据,打开一个文本文件建立一个新的文件
"a"(追加)向文本文件尾部添加数据建立一个新的文件
"rb"(只读)为了输入数据,打开一个二进制文件出错
"wb"(只写)为了输入文件,打开一个二进制文件建立一个新文件
"ab"(追加)向一个二进制文件尾部添加数据建立一个新的文件

"r+"(读写)

为了读和写,打开一个文本文件出错
"w+"(读写)为了读和写,建立一个新的文本文件建立一个新的文件
"a+"(追加)打开一个文本文件,在文件尾部进行读写建立一个新的文件
"rb+"(读写)为了读和写,打开一个二进制文件出错

"wb+"(读写)

"ab+"(追加)

为了读和写,建立一个新的二进制文件

打开一个二进制文件,在文件尾部进行读和写

建立一个新的文件

建立一个新的文件

注:fopen也是会打开失败的,如果打开失败,则返回空指针NULL。打开成功,则返回开辟好后的文件信息区的地址,所以使用前一定要判断一下。

然后就是fclose函数,它是用来关闭文件的,当我们指向文件信息区的FILE*的指针变量pf传进去,关闭好文件后一定要记得将pf置为NULL,因为我们虽然使用fclose函数释放了文件信息区,将文件信息区所占的内存还给操作系统了。但是指针变量pf始终是指向这块内存的,如果解引用访问使用这块内存就是非法访问了,所以当我们关闭文件后就把pf置为NULL。

注:如果以只读" w " 或" wb " 的形式打开文件,如果这个文件本身有数据,则会被清空,因为需要从头写入文件,所以要谨慎的使用只读的形式。

文件的打开方式:

文件打开有两种路径,一种是相对路径,一种是绝对路径

相对路径:

' . '表示当前路径,".."表示上一级路径

如果我们要打开的文件和程序所在的文件在一个路径下的话可以直接使用文件名打开,例如:

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

因为没有路径表示编译器便会自动在程序文件相同路径的位置找该文件。

如果该程序文件在许多级文件内存储,如果我们要打开的文件也在这个多级文件中,但是在程序文件所在文件的上一级的上一级的位置,我们可以这样访问,例如:

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

一个‘ . ‘表示当前路径,两个 ".." 表示上一级路径。

还是将test.txt存放在当前数据文件所在的文件的上一级的上一级的位置,只不过我在这个位置又新建了一个文件夹叫hehe,然后我将test.txt放入这个hehe文件夹中,我们有什么方法可以访问呢:

FILE* pf = fopen(".\\..\\..\\hehe\\test.txt","r");

".\\..\\..\\hehe\\test.txt"意思就是在当前路径 ' . ' 的上一级 " .. " 的上一级" .. " 路径下的文件夹"hehe"里的文件"test.txt"。

绝对路径:

必须填写文件对应的路径,通过这个路径来找到对应的文件

但当我们想要打开其他路径的文件比如桌面上的文件时,我们就需要额外的输入路径,让编译器通过该路径找到对应的文件,例如:

FILE* pf = fopen("C:\\Users\\zpeng\\Desktop\\test.txt","w");
//绝对路径

在文件名前面添加一条路径,就可以根据这个路径找到对应文件。

场景1:当需要打开的文件和当前程序文件都是一个路径时,比如程序文件的项目是需要创建在一个文件夹中的,如果存在同一个文件夹,则不用填写路径。

场景2:当需要打开的文件和程序文件不在同一个文件夹,则需要在文件名前面添加上路径。

总结:文件路径也分为两个,分别是绝对路径相对路径

绝对路径:是在文件和程序文件位置不同时需要填写完整的路径来访问。

相对路径:是和程序文件在同一个文件里的,可能不一级文件,但是位置是有关联的,被称为相对路径

4.4 文件指针的概念

这里要说一下文件是有文件指针的,文件指针决定读取或写入的操作时从哪个位置开始的,如果程序开始运行并且使用过一次函数来访问当前文件信息区的文件了,文件指针就会发生改变,因为文件指针需要访问下一个位置的数据。

假设文件信息区的地址由变量pf来接收,那它的文件指针始终都不会重新开始,方便下一次调用文件访问函数可以从当前位置继续向后访问,所以没访问一次,文件指针会自动向后指向。除非是程序结束、使用rewind函数 或者是 又创建了一个文件信息区,否则当前pf关联的文件的文件指针始终都不会重新指向起始位置。

5、文件的顺序读写

5.1 顺序读写函数介绍
函数名功能适用于
fgetc字符输入函数所有输入流
fputc字符输出函数所有输出流
fgets文本行输入函数所有输入流
fputs文本行输出函数所有输出流
fscanf格式化输入函数所有输入流
fprintf格式化输出函数所有输出流
fread二进制输入文件
fwrite二进制输出文件

以上第三列表格适用于:所有输入流所有输出流文件,意思是每个对应函数的参数里有一个FILE*类型的指针变量参数,也就是流,所以都要有对应的流。所有输入流包括:标准输入流、文件流,所有输出流包括:标准输出流、文件流,二进制文件读写函数只能传文件流。我们也可以使用以上适用于标准输出流的函数数据通过标准输出流输出到屏幕上去,也可以使用以上适用于标准输入流的函数将我们从键盘输入的数据通过标准输入流读取出来,所以要记住,这些函数不仅仅是作用于文件的读取和写入

以上所有函数的声明:

int fputc(int character, FILE* stream);
int fgetc(FILE* stream);
int fputs(const char* str, FILE* stream);
char* fgets(char* str, int num, FILE* stream);
5.1.1 fputc的使用

fputc的声明:

int fputc(int character, FILE* stream);

fputc函数:参数1:character是需要输出的字符。参数2:stream是FILE*类型的指针,可以是标准输出流或者是对应文件的流。

fputc函数的功能:通过参数2的指向的文件信息区里的信息访问文件,并将参数1的字符输出到当前文件,一次只能写一个字符。

fputc函数的使用:

#include <stdio.h>
#include <string.h>
int main()
{FILE* pf = fopen("test.txt", "w");//打开文件if (pf == NULL){perror("fopen");return;}char str[] = "hello world";ine len = strlen(str);int i = 0;for(i = 0; i < len; i++){fputc(str[i], pf);//将"hello world"一个一个输出到文件}fclose(pf);//关闭文件pf = NULL;return 0;
}

那我们也可以通过该函数将字符输出到屏幕上,就像printf一样:

#include <stdio.h>
int main()
{fputc('a',stdout);经过标准输出流直接将字符'a'输出到屏幕上return 0;
}

所以这里也就证明了FILE*类型的指针变量接收的文件信息区的地址是文件的流,顺序读写函数的参数FILE* stream是流,至于什么的流就看自己想怎么操作。

5.1.2 fgetc的使用

fgetc的声明:

int fgetc(FILE* stream);

fgect函数:参数:stream不用说就是流,但仅限于所有输入流,或文件的流,因为fgetc需要从输入流中获取数据。

fgetc函数的功能:将对应的输入流传参过去,getc会读取输入流中的字符,标准输入流是需要我们来输入字符,文件流是fgetc自己读取文件中的字符。

fgetc函数的使用:

#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "r");//打开文件if (pf == NULL){perror("fopen");return;}char c = 0;while(c = fgetc(pf) != EOF)//会不断地向文件后读取数据{printf("%c",c);}fclose(pf);//关闭文件pf = NULL;return 0;
}

那我们也可以通过该函数读取我们键盘输入的字符,就像scanf一样:

#include <stdio.h>
int main()
{char c = fgetc(stdin);printf("%c\n", c);return 0;
}
int c = fgetc(stdin);
等价于 
int c = getchar();

到这里相信大家也都知道了这些函数可以通过标准输入流来获取我们键盘输入的数据或标准输出流将数据输出到屏幕上,那么下面的函数就不用在举这个例子了。

5.1.3 fputs的使用

fputs的声明:

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

fputs函数:参数1:str是需要输出的字符串,参数2:stream是FILE*类型的指针,可以是标准输出流或者是对应文件的流。

fputs函数的功能:将字符串根据输出流输出到对应的位置

fputs函数的使用:

#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return;}char str[] = "hello world";fputs(str, pf);fclose(pf);pf = NULL;return 0;
}

5.1.4 fgets的使用

fgets的声明:

char* fgets(char* str, int num, FILE* stream);

fgets函数:参数1:str是存储fgets从输入流读取的数据空间的地址,参数2:num是需要拷贝从输入流读取的字符的个数,参数3:stream是FILE*类型的指针,可以是标准输入流或者是对应文件的流。

fgets函数的功能:从参数3的输入流中读取num个字符拷贝到str。

如果fgets读取失败会返回一个空指针NULL,所以我们使用该函数时也可以判断一下有没有读取成功。

fgets的使用:

#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return;}char* str = (char*)malloc(10 * sizeof(char));fgets(str, 10, pf);printf("%s\n", str);fclose(pf);pf = NULL;return 0;
}

fgets不管读取多少个字符,最后一定会额外拷贝一个结束字符' \0 ' 放入str中。 

5.1.5 fprintf的使用

fprintf是格式化函数,printf也是格式化函数

fprintf函数的声明:

int fprintf(FILE* stream,const char* format,...);

fprintf和printf有什么区别,我们再看一下printf函数声明:

int printf(const char* format,...);

我们可以发现printf和fprintf之间就差一个参数stream,stream就是流,我们可以将stream的参数修改为文件流,后面的参数就和printf一样,printf本身的输出流是标准输出流stdout,输出到屏幕上的,所以我们就将文件想象成正常使用printf将数据输出到屏幕,其他参数就和printf一样。

如果这样的话,那fprintf可以做到和printf等价:

int main()
{char c = 'a';int a = 10;char str[] = "hello world";printf("%c %d %s",c,a,str);等价于fprintf(stdout,"%c %d %s",c,a,str);return 0;
}

fprintf的使用:

#include <stdio.h>
struct S
{int n;float f;char arr[20];
};
int main()
{struct S s = { 100, 3.14f, "zhangsan" };FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return;}fprintf(pf, "%d %f %s", s.n, s.f, s.arr);fclose(pf);pf = NULL;return 0;
}
5.1.6 fscanf的使用

fscanf和scanf的参数也是相似的,就像fprintf和printf一样:

int fscanf(FILE* stream, const char* format,...);
int scanf(const char* format,...);

fscanf的使用:

#include <stdio.h>
struct S
{int n;float f;char arr[20];
};
int main()
{struct S c = { 0 };FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return;}fscanf(pf, "%d %f %s", &(c.n), &(c.f), c.arr);//输出到变量c中printf("%d %f %s", c.n, c.f, c.arr);fclose(pf);pf = NULL;return 0;
}
5.1.7 fwrite的使用

fwrite函数声明:

size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);

fwrite函数:参数1:ptr是一个const void* 的指针,是可以处理任意类型的数据的地址,不管是整型、浮点型还是结构体类型的地址都可以接收。参数2:size是类型大小,单位是字节。参数3:count是类型变量的个数。参数4:stream必须是文件的流,不能是其他流。

fwrite函数功能:通过参数1的指针将指针指向的count个数量的size类型大小的二进制数据输出到stream流。简单来说就是将数据在内存中的二进制数据传输进流。它的流只能是文件,不能是其他流,例如标准输出流。

#include <stdio.h>
struct S
{int n;float f;char arr[20];
};
int main()
{struct S s = { 200, 3.14f, "zhangsan" };FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "wb");//以二进制写的形式打开文件if (pf == NULL){perror("fopen");return;}////使用fwrite(&s, sizeof(struct S), 1, pf);//以二进制的形式写入文件////关闭文件fclose(pf);pf = NULL;return 0;
}
5.1.8 fread的使用

fread函数声明:

size_t fread(void* ptr, size_t size, size_t count, FILE* stream);

可以看到fread的函数声明和fwrite的函数声明是极其相似的。

fread函数和fwrite函数的区别:不同的就是前面那个void*的指针,fwrite是const修饰的,因为只是想读取它指向的空间里的数据并不想更改,所以使用了const。而fread是需要一个指针,通过这个指针指向的空间来接收读取的值,所以不能是const修饰。

fread函数:参数1:ptr是一个void* 的指针,是可以处理任意类型的数据的地址,不管是整型、浮点型还是结构体类型的地址都可以接收。参数2:size是类型大小,单位是字节。参数3:count是类型变量的个数。参数4:stream必须是文件的流,不能是其他流。

fread函数功能:通过seteam文件流将文件中的count个数量的size类型大小的二进制数据输入到ptr中。简单来说就是将文件中的二进制数据输入到ptr空间。它的流只能是文件,不能是其他流,例如标准输出流。

#include <stdio.h>
struct S
{int n;float f;char arr[20];
};
int main()
{struct S s = { 200, 3.14f, "zhangsan" };FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "rb");//打开文件if (pf == NULL){perror("fopen");return;}////使用struct S c = { 0 };fread(&c, sizeof(struct S), 1, pf);//将文件中二进制的数据读取出来printf("%d %f %s", c.n, c.f, c.arr);////关闭文件fclose(pf);pf = NULL;return 0;
}
5.2 对比一组函数:

scanf / fscanf / sscanf

printf / fprintf / sprintf

  • scanf - 针对标准输入流(stdin)的格式化输入函数
  • printf - 针对标准输出流(stdout)的格式化输出函数
  • fscanf - 针对所有输入流的格式化输入函数
  • fprintf - 针对所有输出流的格式化输出函数

那sscanf和sprintf两个函数是干什么的呢?

sprintf的函数声明:

int sprintf(char* str, const char* format,...)

可以从参数上发现sprintf就比printf多了一个char*类型的参数,那具体功能是什么?

sprintf函数功能:将格式化数据输出到字符串中

sprintf和printf的区别:printf是将格式化数据输出到标准输出流也就是屏幕上,sprintf则是将格式化数据输出到一个字符串里

#include <stdio.h>
struct S
{int n;float f;char arr[20];
};
int main()
{struct S s = { 200, 3.14f, "zhangsan" };char arr[30] = { 0 };sprintf(arr, "%d %f %s", s.n, s.f, s.arr);//将格式化数据输出到字符串arrprintf("%s\n", arr);//打印arr接收到的格式化数据return 0;
}

既然可以使用sprintf函数将格式化数据输出到字符串中,那我们是否可以使用sscanf函数将字符串中的格式化数据提取出来呢?答案是可以的。

sscanf函数声明:

int sscanf(char* str, const char* format,...);

sscanf函数功能:将字符串中的格式化数据读取出来

sscanf和scanf的区别:scanf是将格式化数据输入到标准输入流也就是屏幕上,sscanf则是将格式化数据从字符串里读取出来。

#inlcude <stdio.h>
struct S
{int n;float f;char arr[20];
};
int main()
{//将格式化的数据输出到字符串数组arr中struct S s = { 200, 3.14f, "zhangsan" };char arr[30] = { 0 };sprintf(arr, "%d %.2f %s", s.n, s.f, s.arr);//将格式化数据输出到字符串arrprintf("%s\n", arr);//从arr这个字符串中读取出格式化的数据struct S c = { 0 };sscanf(arr, "%d %f %s", &c.n, &c.f, c.arr);printf("%d %f %s", c.n, c.f, c.arr);return 0;
}

6、文件的随机读写

 什么是文件的随机读写?文件的随机读写就是定位到我们想要的位置开始向后读写,从开头向后读写就是顺序读写。定位位置向后读写就是随机读写。

6.1 fseek
int fseek(FILE* stream, long int offset, int origin);

fseek函数:参数1就是stream文件的流。参数2offset就是偏移量,是某个位置开始的向后的偏移量处的位置开始向后读写。而参数三origin就是决定这某个位置。

参数3:origin有三种位置:

ContstantReference  position
SEEK_SETBeginning  of  file (文件的起始位置)
SEEK_CURCurrent  position  of  the  file  pointer(文件指针的当前位置)
SEEK_ENDEnd of file(从文件的末尾位置向前偏移)

是从这些位置开始向后计算偏移量的位置,从计算好偏移量的位置开始向后读取。

例子:

#include <stdio.h>
int main()
{FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");if (pf == NULL){perror("fopen");return;}////使用fseek(pf, 6, SEEK_SET);//文件指针位置:起始位置向后偏移6个偏移量位置int ch = fgetc(pf);//读取当前文件指针位置的字符printf("%c", ch);fseek(pf, -3, SEEK_END);//文件指针位置:文件末尾向前偏移3个偏移量位置int ch = fgetc(pf);//读取当前文件指针位置的字符printf("%c", ch);fseek(pf, 5, SEEK_CUR);//文件指针位置:当前文件指针位置向后偏移5个偏移量位置int ch = fgetc(pf);//读取当前文件指针位置的字符printf("%c", ch);////关闭文件fclose(pf);pf = NULL;return 0;
}

文件里是存在文件指针的,正常情况下调用一次后该文件指针会向后指向,下一次调用是从后面继续向后访问。顺序读写函数是这样的。而随机读写函数是可以随机改变文件指针的指向,让文件指针改变位置从而进行读取或写入。

注:

1. 文件指针并不是我们熟知的C语言指针,而是一个表示文件位置的指针。

2. 偏移量为负数是向前偏移,偏移量为整数是向后偏移。

3. 不管文件指针的位置如何改变,文件都是自动的从前向后访问 

6.2 ftell

ftell的函数声明:

long int ftell(FILE* stream);

如果我们不知道当前的文件初始位置与文件指针之间的偏移量是多少时我们就可以使用ftell库函数,这个函数会计算好文件指针的偏移量并返回。

例子:

#include <stdio.h>
int main()
{FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");if (pf == NULL){perror("fopen");return;}//fseek(pf, -3, SEEK_END);//文件指针位置:文件末尾向前偏移3个偏移量位置int ch = fgetc(pf);//读取当前文件指针位置的字符printf("%c\n", ch);int ret = stell(pf);//计算当前偏移量printf("%d\n",ret);////关闭文件fclose(pf);pf = NULL;return 0;
}

6.3 rewind

让文件指针的位置回到文件的起始位置

比如我随意用fseek来设置文件指针的位置导致乱了套,这时我们就可以使用rewind来让文件指针回到起始位置,功能比较简单,容易理解。

void rewind(FILE* stream);

例子:

#include <stdio.h>
int main()
{FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");if (pf == NULL){perror("fopen");return;}//fseek(pf, -3, SEEK_END);//文件指针位置:文件末尾向前偏移3个偏移量位置int ch = fgetc(pf);//读取当前文件指针位置的字符printf("%c\n", ch);//不知道当前文件指针的位置就重置rewind(pf);//重置文件指针位置int ch = fgetc(pf);//读取起始位置字符printf("%c\n",ch);////关闭文件fclose(pf);pf = NULL;return 0;
}

7、文件读取结束的判定

7.1 被错误使用的feof

牢记:在文件读取过程中,不能用 feof 函数的返回值直接来判断文件是否结束。

 feof 的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。

文件读取结束有两种原因:

1. 文件遇到末尾了

2. 文件读取错误了

1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc的错误),或者是NULL(gets的错误)

例如:

  • fgetc判断是否为EOF
  • fgets判断是否问NULL

2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。

例如:

  • fread判断返回值是否小于实际要读的个数

注:fread的返回值是读取到的元素的个数。

7.2 ferror

feof是判断文件是否是因为读取到文件末尾而结束的,而ferror则是判断是否是因为读取失败而结束的,如果读取失败结束就返回1.

int ferror(FILE* stream);

文本文件读取结束判断:

#include <stdio.h>
#include <stdlib.h>
int main()
{int ch = 0;FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");if (pf == NULL){perror("fopen");return;}//while (ch = fgetc(pf) != EOF){printf("%c ", ch);}printf("\n");//判断是什么原因结束的if (ferror(pf))//判断是否是读取失败导致结束的{puts("1/0 error when reading");}else if (feof(pf))//判断是否是读取到文件末尾结束的{printf("End of file reached successfully");}////关闭文件fclose(pf);pf = NULL;return 0;
}

二进制文件的例子:

#include <stdio.h>
int main()
{double a[5] = { 1.0, 2.0, 3.0, 4.0, 5.0 };FILE* pf = fopen("test.bin", "wb");//以输出二进制的形式打开fwrite(a, sizeof *a, 5, pf);fclose(pf);//double b[5];pf = fopen("test.bin", "rb");//以读取二进制的形式打开size_t ret_code = fread(b, sizeof *b, 5, pf);if (ret_code == 5){puts("Array read successfully,contents: ");for (int n = 0; n < 5; n++){printf("%f ", b[n]);}putchar('\n');}else{//判断是什么原因结束的if (ferror(pf))//判断是否是读取失败导致结束的{puts("1/0 error when reading");}else if (feof(pf))//判断是否是读取到文件末尾结束的{printf("End of file reached successfully");}}////关闭文件fclose(pf);pf = NULL;return 0;
}

8、文件缓冲区

ANSIC 标准规定采用 "缓冲文件系统" 处理数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块 "文件缓冲区" ,从内存中向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上,如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译器系统决定的。

#include <stdio.h>
#include <windows.h>
int main()
{FILE* pf = fopen("test.txt", "w");fputs("abcdef", pf);printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区的函数,才将输出缓冲区的数据写到文件(磁盘)//注: fflush 函数在高版本的VS不能使用了printf("再睡眠10秒-此时再打开test.txt文件,发现文件有内容了\n");Sleep(10000);fclose(pf);//注:fclose关闭文件时,也会刷新缓冲区pf = NULL;return 0;
}

这里可以得出一个结论:

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束时关闭文件,如果不做,可能导致读写文件问题。

C语言第15篇:文件操作到这里也就结束了,我们下一篇笔记再见,拜拜-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/28603.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PL理论】(29) OOP:面向对象编程 | 案例研究:C++ 中的类 | 继承 | 继承和指针 | Object-oriented Programming

&#x1f4ad; 写在前面&#xff1a;本章我们将进入 Object-oriented Programming&#xff0c;面向对象编程的讲解&#xff0c;探讨 C 中的类&#xff0c;继承等。 目录 0x00 面向对象编程 0x01 C语言中的结构体 0x02 案例研究&#xff1a;C 中的类 0x03 术语 0x04 继承&…

PHP调用阿里云OSS的SDK封装成服务的完整指南与问题解决

在现代Web开发中&#xff0c;使用云存储来管理和存储大量的静态文件已经成为常态。阿里云OSS&#xff08;对象存储服务&#xff09;是其中一个非常受欢迎的选择。在这篇文章中&#xff0c;我们将详细讲解如何在PHP项目中集成并使用阿里云OSS SDK。 #### 一、前期准备 在开始之…

vue3轮播图怎么做

先看效果 实现代码 <n-carouseleffect"card"dot-type"line"draggable:autoplay"!isHovered":current-index"currentIndex"prev-slide-style"transform: translateX(-150%) translateZ(-450px);opacity:1"next-slide-st…

【MySQL】(基础篇十三) —— 联结

联结 本文介绍什么是联结&#xff0c;为什么要使用联结&#xff0c;如何编写使用联结的SELECT语句。介绍如何对被联结的表使用表别名和聚集函数。 SQL最强大的功能之一就是能在数据检索查询的执行中联结&#xff08;join&#xff09;表。联结是利用SQL的SELECT能执行的最重要…

springboot+vue+mybatis教师工作审核系统+PPT+论文+讲解+售后

随着社会不断进步与发展&#xff0c;生活节奏不断加快&#xff0c;信息已经成为我们生活中不可缺少的一部分&#xff0c;很多学校需要掌握大量的信息来了解特定学生的需求&#xff0c;传统的做法是组织大量的人力物力对学生散发调查表&#xff0c;然后对收集的信息进行统计并得…

基于Matlab的BP神经网络的车牌识别系统(含GUI界面)【W7】

简介&#xff1a; 本系统结合了图像处理技术和机器学习方法&#xff08;BP神经网络&#xff09;&#xff0c;能够有效地实现车牌的自动识别。通过预处理、精确定位、字符分割和神经网络识别&#xff0c;系统能够准确地识别各种车牌图像&#xff0c;并在智能交通管理、安防监控等…

LeetCode 338.比特位计数

各位朋友们&#xff0c;大家好啊&#xff0c;今天此题我用的方法比较好理解&#xff0c;但时间复杂度比较高如果大家觉得可以的话&#xff0c;不妨给个免费的赞吧&#xff0c;谢谢了^ _ ^ 1.题目要求如图所示: 2.做题步骤: 1.先计算总共多少个数: int count 0;int number 0;…

25 avl树

目录 底层结构avl树的概念节点定义插入旋转验证删除全性能 1. 底层结构 前面对map/multimap/set/multiset进行了简单的介绍&#xff0c;在其文档介绍中发现&#xff0c;这几个容器有几个共同点是&#xff1a;其底层都是按照二叉搜索树来实现的&#xff0c;但是二叉搜索树有自…

用Copilot画漫画,Luma AI生成视频:解锁创意新玩法

近年来&#xff0c;随着人工智能技术的不断发展&#xff0c;各种创意工具也层出不穷。今天&#xff0c;我们就来介绍一种全新的创作方式&#xff1a;使用Copilot画漫画&#xff0c;再将漫画放入Luma AI生成视频。 Copilot&#xff1a;你的AI绘画助手 Copilot是一款基于人工智…

使用sherpa-ncnn进行中文语音识别(ubuntu22)

获取该开源项目的渠道&#xff0c;是我在b站上&#xff0c;看到了由csukuangfj制作的一套语音识别视频。以下地址均为csukuangfj在视频中提供&#xff0c;感谢分享&#xff01; 新一代Kaldi RISC-V: VisionFive2 上的实时中英文语音识别_哔哩哔哩_bilibili 开源项目地址&…

如何将扫描的 PDF 转换为 Word

您是否正在寻找一种可靠且轻松的方式将扫描的 PDF 文档转换为可编辑的 Word 文件&#xff1f;要将 PDF 转换为可编辑的 Word 文档&#xff0c;神奇之处在于光学字符识别(OCR)。 使用 PDFgear&#xff0c;您可以无缝地将扫描的 PDF 转换为 Word&#xff0c;无论是在线还是离线。…

内网Docker镜像无法使用?Debian/Ubuntu离线安装Dokcer

离线安装Docker 卸载冲突的包 for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done先删除docker sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin d…

CLIP-guided Prototype Modulating for Few-shot Action Recognition

标题&#xff1a;基于CLIP引导的原型调制用于少样本动作识别 源文链接&#xff1a;CLIP-guided Prototype Modulating for Few-shot Action Recognition | International Journal of Computer Vision (springer.com)https://link.springer.com/article/10.1007/s11263-023-019…

awd工具安装

fscan(漏洞扫描) 下载 下载地址: Releases shadow1ng/fscan GitHub 把下载的文件放到指定文件目录里, 在文件的位置打开cmd 输入 fscan64.exe -h 192.168.1.1/24 ok了 接下来说说fscan的使用 使用 1.信息搜集: 存活探测(icmp) 端口扫描 2.爆破功能: 各类服务爆破(…

【R语言】数据可视化分析和统计检验——线性和线性混合效应模型

R语言数据可视化分析和统计检验 写在前面1、数据读取及分析2、组间均值和标准差统计分析3、图像数据探索3.1 图像绘制&#xff08;查看是否存在极端数据&#xff0c;以及数据分布情况&#xff09;3. 2 数据标准化&#xff08;Z-scores&#xff09;3.3 绘制数据相关性 4、ggplot…

Axios基础用法

目录 Axios简介&#xff1f; json-server 下载json-server 创建模拟数据json文件 运行json-server ​编辑​编辑 安装Axios Axios基础用法 创建Vue项目 get请求 post请求 put请求 delete请求 并发请求 总结 Axios简介&#xff1f; Axios是一个基于Promise的HTTP库&#xf…

ComfyUI

文章目录 一、关于 ComfyUI特点快捷键QA你为什么做这个&#xff1f;这是给谁的&#xff1f; 二、安装1、Windows直接链接下载如何在另一个UI和ComfyUI之间共享模型&#xff1f; 2、Jupyter Notebook3、手动安装&#xff08;Windows、Linux&#xff09;AMD GPU&#xff08;仅Lin…

获取wav音频文件时长部署问题

在Linux服务器上运行Java代码时&#xff0c;如果涉及到音频处理&#xff0c;可能会遇到一些在Windows上不存在的问题。尤其是在处理音频文件时&#xff0c;javax.sound.sampled.Clip接口在Linux上的兼容性可能会有问题。这是因为Clip依赖于底层的音频系统&#xff0c;而这些系统…

ubuntu搭建java开发环境IDEA版

一.安装 OpenJDK 更新包列表&#xff1a; sudo apt update安装 OpenJDK&#xff1a; 你可以选择安装不同版本的 OpenJDK&#xff0c;例如 11 或 17&#xff0c;这个是安装 OpenJDK 11 的命令&#xff1a; sudo apt install openjdk-11-jdk验证安装&#xff1a; 安装完成后…

SpringBoot实现的大文件上传

前言 大文件分片上传和断点续传是为了解决在网络传输过程中可能遇到的问题&#xff0c;以提高文件传输的效率和稳定性。 首先&#xff0c;大文件分片上传是将大文件分割成较小的片段进行上传。这样做的好处是可以减少单个文件的传输时间&#xff0c;因为较小的文件片段更容易快…