1.为什么使用文件
目录
文件(file)是存储器中存储信息的区域,文件一般被保存在磁盘光盘等各种储存设备上(外存上),数据可以在上面被长久的保存下来。
2.什么是文件
拿我们的电脑来说被存储在磁盘或硬盘上的就是文件,我们程序设计中常用到的文件是程序文件和数据文件
2.1程序文件
程序文件包括:源文件(.c ,.cpp,.java等),目标文件(windows环境下后缀为.obj),可执行程序(windows环境下后缀为.exe)
2.2数据文件
它里边不一定是程序。程序在执行中额可能需要进行数据的读取,写入,它储存着程序运行时所需的各种信息。
2.3文件名字
文件都有着各自的唯一标识符:文件名,便于使用与识别
文件名一般由三部分组成:文件路径+文件主干+文件后缀
例:c:code\test.txt
3.二进制文件与文本文件
根据组织形式的不同,数据文件又被分为数据文件和文本文件
二进制文件
不加以转换的将二进制数据储存在文件中,这种文件打来你是看不懂的
文本文件
要求以ASCII字符的形式储存,在储存前都会进行转换储存在文件中,这种文本文件打开能看的懂
数据在文件中是如何存放
字符:一律以ASCII新式存储
数值:既可以用ASCII形式存储,也可以使用二进制的形式存储
例:100000的存储
4.文件的打开和关闭
4.1 流
在c语言中流是一个非常重要和抽象的概念
简单理解,流可以视为流动着数据的河
河的源头可以是键盘,文件,网络上,光盘等
河的目标可以是屏幕,文件,硬盘,网络上,U盘等
流可以帮助我们对来自不同地方的数据进行转化,通过流我们可以方便的读取和写入数据
4.2标准流
在c语言中,默认打开了3个流
• stdin-标准输入流:在大多数系统中,它通常默认定向到键盘。
• stdout-标准输出流:在大多数系统中,它通常默认定向到文本控制台(通常在屏幕上)
c语言提供了标准函数来支持对流的操作,使得程序能够与外部设备(键盘,显示器,文件)进行交互
例如:scanf函数 从标准输入流中读取数据
printf函数 将信息输出到标准输出流中
注意:至于数据是怎么样输入到内存中,怎么样输出到屏幕上,那是流该考虑的事情,我们只需要对流进行操作即可
•stderr-标准错位流:⼤多数环境中将错误信息输出到控制台界面。
4.3文件指针
缓冲⽂件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
“文件指针”,指向一个与文件相关的结构体,这个结构体包含了文件读写的重要信息,如:文件名
,文件状态,文件当前位置。
“文件指针”由系统声明,取名为:FILE
例如在vs2013中stdio.h中有FILE声明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
不同类型的c编译器对FILE的定义不完全相同,但是大同小异
每当打开一个文件的时候,系统都会自动创建一个FILE类型的结构体变量并且自动填充里面的信息
一般我们都是通过一个FILE结构体类型的指针来维护这个结构体变量,这样使用起来会很方便
一个FILE*型的指针创建:
FILE* pf;//⽂件指针变量
通过对文件指针,我们就可以对文件进行各种操作,如定位文件位置,读取文件内容,向文件写入数据
4.4 文件的打开和关闭
文件的要进行读写要先打开文件,使用结束后应关闭(不然会占用内存资源)
文件操作的进行顺序:打开文件→文件读写操作→关闭文件
ANSI C规定用fopen打开文件,fclose关闭文件
fopen
原型:FILE * fopen ( const char * filename, const char * mode );
打开文件
打开其名称在参数 filename 中指定的文件,并将其与流相关联
参数:
const char * filename :需要打开的文件名
const char * mode: 表⽰⽂件的打开模式,下⾯都是⽂件的打开模式(mode是个字符串):
"r" | 读:打开文件进行输入操作。该文件必须存在(否则出错)。 |
"w" | 写:为输出操作创建一个空文件。如果已存在同名文件,则丢弃其内容,并将该文件视为新的空文件。 |
"a" | 附加:打开文件以在文件末尾输出。输出操作始终在文件末尾写入数据,并展开文件。重新定位操作(fseek、fsetpos、rewind)将被忽略。如果文件不存在,则创建该文件。 |
"r+" | 阅读/更新:打开要更新的文件(用于输入和输出)。该文件必须存在。 |
"w+" | 写入/更新:创建一个空文件并打开它进行更新(用于输入和输出)。如果同名文件已存在,则其内容将被丢弃,并将该文件视为新的空文件。 |
"a+" | 追加/更新:打开要更新的文件(用于输入和输出),所有输出操作都将在文件末尾写入数据。重新定位操作(fseek、fsetpos、rewind)会影响下一个输入操作,但输出操作会将位置移回文件末尾。如果文件不存在,则创建该文件。 |
以上这些打开方式,打开的文件都被视为文本文件。想要进行二进制的写入,我们要把文件视为二进制文件进行打开,字符串中必须包含“b”字符。这个额外的“b”字符可以附加在字符串的末尾(从而形成以下复合模式:“rb”、“wb”、“ab”、“r+b”、“w+b”、“a+b”),也可以插入字母和混合模式(“rb+”、“wb+”、“ab+”)的“+”符号之间。
返回值:
当我们打开一个文件的时候,这个文件就变成了一个流,简称文件流。
打开成功fopen的返回值是一个指向这个流的指针通过它并将其与流相关联
打开失败将返回 null 指针
使用:
#include<stdio.h>
int main() {
FILE* pf;
//打开文件
pf = fopen("test.txt", "w");
return 0;
}
fclose
关闭文件原型:int fclose ( FILE * stream );
关闭与流关联的文件并取消关联。
所有与流关联的内部缓冲区都将与流解除关联并刷新:任何未写入的输出缓冲区的内容将被写入,而任何未读的输入缓冲区的内容将被丢弃。
参数:
FILE * stream :指向标识输入流的FILE对象的指针。
返回值:
如果流被成功关闭,则返回一个零值。如果失败,则返回EOF。
使用:
#include<stdio.h>
int main() {
FILE* pf;
//打开文件
pf = fopen("test.txt", "w");
//关闭文件
fclose(pf);
pf = NULL; //避免野指针的出现
return 0;
}
5.文件的顺序读写
读:把文件数据读取至程序中
写:把程序的数据输出到文件中
5.1文件读取结束原因的判定
文件在被打开时会自动生成俩个指示器:eof指示器(feof)错误指示器(ferror)
eof指示器(feof):判断当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。
错误指示器(ferror):判断当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到错误读取结束。
只要往函数里传入文件流的指针即可判断,文件的读取或写入是因为什么原因导致的结束
5.2顺序读写函数总览:
函数名 | 功能 | 适⽤于 |
fgetc | 字符输⼊函数 | 所有输⼊流 |
fputc | 字符输出函数 | 所有输⼊流 |
fgets | ⽂本⾏输⼊函数 | 所有输⼊流 |
fputs | ⽂本⾏输出函数 | 所有输⼊流 |
fscanf | 格式化输⼊函数 | 所有输⼊流 |
fprintf | 格式化输出函数 | 所有输⼊流 |
fread | ⼆进制输⼊ | ⽂件流 |
fwrite | ⼆进制输出 | ⽂件流 |
fgetc
fgetc 函数原型:int fgetc ( FILE * stream );
从流中获取字符
文件内部读取从位置指示器开始读取,读取成功位置指示器将前进到下一个字符。
参数:
FILE * stream 指向标识输入流的FILE对象的指针
返回值:
如果成功,则返回所读取的字符(提升为int值)。返回ASCII码值
为了适应返回失败返回EOF(-1)所以返回值设置为int类型
如果位置指示器位于文件末尾,则函数返回 EOF(-1) 并设置流 的 eof 指示器(feof)。
如果发生其他读取错误,该函数也会返回 EOF(-1),但会设置其错误指示器 (ferror)。
使用:
int main() {
FILE* pf;
//读的方式打开文件
pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 0;
}
//读文件
int ch;
while ((ch = fgetc(pf)) != EOF) {//利用函数返回值作为结束循环条件
printf("%c\n", ch); //即可将全部字符读出来
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputc
fputc 函数原型:int fputc ( int character, FILE * stream );
将字符写入流
将一个字符写入流并推进位置指示器。字符被写入流的内部位置指示器所指示的位置,然后自动向前移动一个。
参数:
int character : 要写入的字符的 int 提升。写入时,该值在内部转换为无符号字符。
FILE * stream :指向标识输入流的FILE对象的指针
返回值:
成功后,将返回所写字符。
如果发生写入错误,则返回 EOF 并设置错误指示器 (ferror)。
使用:
int main() {
FILE* pf;
//写的方式打开文件
pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen");
return 0;
}
//写文件
char ch;
for (ch = 'a'; ch <= 'z'; ch++) {
fputc(ch, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgets
fgets 函数原型:char * fgets ( char * str, int num, FILE * stream );
从流中获取字符串
从流中读取字符,并将它们作为字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件末尾,以先发生者为准。
换行符使 fgets 停止读取,但它被函数视为有效字符,并包含在复制到 str 的字符串中。
终止 null 字符会自动附加到复制到 str 的字符之后。
参数:
char * str: 指向复制读取的字符串的 char数组的指针。
int num: 要复制到 str 中的最大字符数(包括终止 null 字符)。
FILE * stream: 指向标识输入流的 FILE 对象的指针。stdin可以用作从标准输入读取的参数。
返回值:
成功后,该函数返回 str
如果在尝试读取字符时遇到文件末尾,则设置 eof 指示器 (feof)。如果在读取任何字符之前发生这种情况,则返回的指针为空指针(并且 str 的内容保持不变)。
如果发生读取错误,则设置错误指示器 (ferror),并返回 null 指针(但 str 指向的内容可能已更改)。
使用:
int main() {
FILE* pf;
//读的方式打开文件
pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 0;
}
//读文件
char arr[20] = { 0 };
fgets(arr, 20, pf);
printf("%s", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputs
fputs 函数原型:int fputs ( const char * str, FILE * stream );
将字符串写入流
将 str 指向的字符串写入流。
该函数从指定的地址 (str) 开始复制,直到到达终止 null 字符 ('\0')。此终止 null 字符不会复制到流中。
参数:
const char * str: str指向的字符串,其中包含要写入流的内容。
FILE * stream: 指向标识输出流的 FILE 对象的指针。
返回值:
成功后,将返回一个非负值。
出错时,该函数返回 EOF 并设置错误指示器 (ferror)。
使用:
int main() {
FILE* pf;
//读的方式打开文件
pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen");
return 0;
}
//写文件
char arr[20] = { 'a','b','c','d','e','f','g' };
fputs(arr,pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fscanf
fscanf 函数原型:int fscanf ( FILE * stream, const char * format, ... );
从流中读取格式化数据
参数:
FILE * stream : 指向 FILE 对象的指针,该对象标识要从中读取数据的输入流。
const char * format, ... : 有格式的数据
返回值:
成功后,该函数返回成功填充的参数列表的项数。此计数可能与预期的项目数匹配,也可能由于匹配失败、读取错误或文件末尾的范围而更少(甚至为零)。
如果在读取时发生读取错误或到达文件末尾,则设置正确的指示器(feof 或 ferror)。
而且,如果在成功读取任何数据之前发生任何情况,则返回 EOF。
如何使用
当你会使用scanf函数的时候,你基本也会使用fscanf函数了
scanf
从stdin-标准输入流里获取有格式的数据,大多都是从键盘上获取
int main() {
int a = 0;
//从键盘上获取a的值
scanf("%d", &a);
return 0;
}
fscanf
从文件流里获取数据
第一个参数就是指向着对应的流
第二个参数就和scanf一样了,是有格式的数据
struct Student {
char name[20];
int age;
int score;
};
int main() {
FILE* pf;
struct Student s1 = { 0 };
//读的方式打开文件
pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 0;
}
//以有格式的方式读文件
fscanf(pf, "%s %d %d", s1.name, &s1.age, &s1.score);
//打印出来看看
printf("%s %d %d", s1.name, s1.age, s1.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
test.txt里的内容:
fprintf
fprintf 函数原型:int fprintf ( FILE * stream, const char * format, ... );
将格式化数据写入流式处理
参数:
FILE * stream :指向标识输出流的 FILE 对象的指针。
const char * format, ... : 有格式的数据
返回值:
成功后,将返回写入的字符总数。
如果发生写入错误,则设置错误指示器 (ferror) 并返回负数。
如果写入宽字符时发生多字节字符编码错误,则 errno 设置为 EILSEQ 并返回负数。
如何使用
当你会使fprintf函数的时候,你基本也会使用fprintf函数了
printf
将有格式的数据,输出到stdout-标准输出流中,它通常默认定向在屏幕上
int main() {
int a = 10;
//将a输出到屏幕上
printf("%d", a);
return 0;
}
fprintf
将数据通过文件流,将数据输出到文件中
struct Student {
char name[20];
int age;
int score;
};
int main() {
FILE* pf;
struct Student s1 = { "张三",18 ,100};
//写的方式打开文件
pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen");
return 0;
}
//以有格式的方式写入文件
fprintf(pf, "%s %d %d", s1.name, s1.age ,s1.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fread
fread 函数原型:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
从流中读取数据块
从流中读取一个由count元素组成的数组(每个元素的大小为size字节)并将它们存储在ptr指定的内存块中。
文件的位置指示器按读取的总字节数前进。
如果成功读取的总字节数为(size*count)。
参数:
void * ptr : 指向大小至少为 (size*count) 字节的内存块的指针,指针类型强制转换为 void*
size_t size : 要读取的每个元素的大小(以字节为单位)
size_t count : 元素的个数
FILE * stream : 指向指定输入流的 FILE 对象的指针
返回值:
返回成功读取的元素总数。
如果返回值与 count 参数不同,则表示读取时发生读取错误或到达文件末尾。在这两种情况下,都设置了正确的指标,可以分别使用 ferror 和 feof 进行检查。
如果 size_t 或 count 为零,则该函数返回零,并且 ptr 指向的流状态和内容保持不变
使用:
int main() {
FILE* pf;
int arr[5] = { 0 };
//读的方式打开文件
pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 0;
}
//以二进制的形式读文件
fread(arr, 4, 5, pf);
//打印出来看看
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
text.txt里的内容:
实际上里面放的是二进制形式的1 2 3 4 5
fwrite
fwrite 函数原型:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
写入要流式传输的数据块
将由count元素组成的数组(每个元素的大小为size字节)从ptr所指向的内存块写入流中的当前位置。
流的位置指示器按写入的总字节数前进。
参数:
const void * ptr : 指向要写入的元素数组的指针,指针类型强制转换为 const void*
size_t size : 要写入的每个元素的大小(以字节为单位)
size_t count : 元素的个数
FILE * stream : 指向指定输出流的 FILE 对象的指针
返回值:
返回成功写入的元素总数。
如果返回值与 count 参数不同,则写入错误会阻止函数完成。在这种情况下,将为流设置错误指示器 (ferror)。
如果 size_t 或 count 为零,则函数返回零,错误指示器保持不变。
使用:
int main() {
FILE* pf;
int arr[5] = { 1,2,3,4,5 };
//写的方式打开文件
pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen");
return 0;
}
//以二进制的形式写入文件
fwrite(arr, 4, 5, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
5.3对比一组函数
| scanf/fscanf/sscanf | printf/fprintf/sprintf
5.4使用fputc打印字符
前面已经说过fputc适合所有输出流,我们能使用文件流把数据输入进文件里
那么我们能不能使用stdout - 标准输出流把数据输入到屏幕上呢?答案是可以的
int main() {
//写到屏幕上
char ch;
for (ch = 'a'; ch <= 'z'; ch++) {
fputc(ch, stdout);//使用标准输出流 - stdout
}
return 0;
}
输出结果:abcdefghijkl..........
5.5使用fgetc读取字符
fgetc适应所有输入流,所以理应它也可以从键盘上读取数据
int main() {
//从键盘读取数据
char ch;
ch = fgetc(stdin);
printf("ch = %c", ch);
}
6.文件的随机读写
fseek
fseek 函数原型:int fseek ( FILE * stream, long int offset, int origin );
重新定位流位置指示器
参数:
FILE * stream : 指向标识流的 FILE 对象的指针
long int offset : 偏移量
int origin : 位置指示器的起始位置
SEEK_SET | 文件开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件末尾 |
返回值:
如果成功,该函数将返回零。
否则,它将返回非零值。
如果发生读取或写入错误,则设置错误指示器 (ferror)
怎么使用
int main() {
FILE* pf;
//读的方式打开文件
pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 0;
}
//读文件
char ch;
ch = fgetc(pf);
printf("%c", ch);
fseek(pf, 4, SEEK_SET); //重新定位流位置指示器
ch = fgetc(pf);
printf("%c", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
test.txt文件里内容:
abcdefghi
如何读取
第一次读取:
使用fseek重新定位流位置指示器后读取:
ftell
返回位置指示器相对于起始位置的偏移量 参数: FILE * stream :指向标识流的 FILE 对象的指针 返回值:ftell 函数原型:long int ftell ( FILE * stream );
成功后,返回位置指标的当前值。
失败时,返回 -1L,并将 errno 设置为特定于系统的正值。
int main() {
FILE* pf;
//读的方式打开文件
pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 0;
}
//计算位置指示器相对于起始位置的偏移量
int ret;
fseek(pf, 4, SEEK_END); //重新定位流位置指示器
ret = ftell(pf);
printf("%d", ret);//返回⽂件指针相对于起始位置的偏移量
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
test.txt文件里内容:
abcdefghi
计算⽂件指针相对于起始位置的偏移量:
也就是说将位置指示器的位置设置为文本末尾,这样你就可以算出这个文本一共有多少字符了
rewind
将流的位置指示器设置为开头rewind 函数原型:void rewind ( FILE * stream );
将与流关联的位置指示器设置为文件的开头。
在成功调用此函数后,与流相关的文件结束(feof)和错误内(ferror)指示器将被清除
参数:
FILE * stream : 指向标识流的 FILE 对象的指针
返回值:
无
使用:
int main() {
FILE* pf;
//读的方式打开文件
pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 0;
}
//读文件
int ch;
while ((ch = fgetc(pf)) != EOF) {
printf("%c\n", ch);
}
//让位置指示器回归文件开头
rewind(pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
7.被错误使用的 feof
牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。 feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。 1. ⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets ) 例如: • fgetc 判断是否为 EOF . • fgets 判断返回值是否为 NULL 2. ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。 例如: • fread判断返回值是否⼩于实际要读的个数。8.文件缓冲区
在C语言中,文件缓冲区是一个内存区域,用于存储即将写入文件或从文件读取的数据。这个缓冲区的引入主要是为了提高文件操作的效率。
当我们从文件读取数据时,如果每次只读取一个字节或一个字符,那么磁盘I/O操作的次数会非常频繁,这将大大降低程序的执行效率。因此,C语言使用了一个缓冲区来存储从文件读取的数据。当程序需要从文件读取数据时,它会首先从缓冲区中读取,只有当缓冲区中的数据被完全读取后,才会再次从磁盘中读取数据并填充缓冲区。类似地,当程序需要写入数据时,也是先将数据写入缓冲区,只有当缓冲区满时,才会将缓冲区中的数据一次性写入磁盘。
这种缓冲机制可以大大减少磁盘I/O操作的次数,从而提高文件操作的效率。需要注意的是,缓冲区的大小是有限的,如果缓冲区中的数据没有被及时读取或写入,可能会导致数据丢失或覆盖。因此,在使用文件缓冲区时,需要注意及时清空缓冲区,以确保数据的完整性。
在C语言中,可以通过标准库函数来操作文件缓冲区
例如
fflush()
函数用于清空缓冲区,setvbuf()
函数用于设置缓冲区的大小和类型等。
#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
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语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭文件。
如果不做,可能导致读写⽂件的问题。
完结撒花
标签:文件,读取,int,写入,详解,pf,FILE,操作 From: https://blog.csdn.net/2301_78957570/article/details/137209371