文件是存在电脑的硬盘上的,使用文件是为了将数据进行"持久化"的保存(这里只要硬盘不损坏,数据就会一直存在),如果没有文件,退出程序,数据就会被销毁
文件:磁盘(硬盘)上的文件就是文件,在程序设计中,文件分为程序文件和数据文件(从文件功能分类)
包括源程序文件(.c),目标文件(.obj)(Windows),可执行文件(.exe)
存的是程序运行时读写的数据,这里我们讨论的也是数据文件
三个部分:文件路径+文件名主干+文件后缀(文件名主干经常被称为文件)
根据数据的组织形式,我们将文件分为文本文件和二进制文件
二进制文件:数据在内存中以二进制的形式存储,不加转换的输出到外存文件中(一般看不懂)
文本文件:以ASCII字符的形式存储的文件(我们能看懂)
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出 操作各不相同,为了⽅便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流 想象成流淌着字符的河。
C程序针对文件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。 ⼀般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。
既然c程序需要通过打开关闭流来操作,那我们为什么在之前没有这样的操作呢?
这是因为C语言程序在启动时默认打开了三个流(及标准流)
stdin 标准输入流 大多从键盘输入(scanf)
stdout 标准输出流,大多到显示器(printf)
stderr 标准错误流,输出到显示器
这三个流的类型是FILE* (文件指针)
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息,这个信息保存在一个结构体变量中,该结构体变量由系统说明,取名FILE
定义pf是一个指向FILE类型数据的指针变量(通过指针变量间接找到与他关联的文件)
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。 在编写程序的时候,在打开文件的同时,都会返回⼀个FILE*的指针变量指向该文件,也相当于建立了 指针和文件的关系。 ANSI C 规定使用fopen 函数来打开文件, fclose 来关闭文件。
FILE* fopen(const char*filename,const char*mode)
功能:打开filename的文件,同时将打开的文件和一个流相关联,后续对流的操作通过fopen返回的文件指针进行操作
filename 可以是相对路径(就是和当前程序所在文件的关系),也可以是绝对路径
mode是打开操作方式
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入打开一个已经存在的文件 | 出错 |
“w”(只写) | 为了输出,打开一个文件(会清理) | 建立新的文件 |
“a”(追加) | 向文本文件尾添加 | 建立新的文件 |
“rb”(只读) | 为了输入打开一个已经存在的文件(二进制) | 出错 |
“wb”(只读) | 为了输出,打开一个文件(会清理)(二进制) | 建立新的文件 |
“ab”(只读) | 向文本文件尾添加(二进制) | 建立新的文件 |
“r+”(只读) | 为了读和写打开一个文件 | 出错 |
“w+”(只读) | 为了读和写建立一个文件 | 建立新的文件 |
“a+”(只读) | 打开一个文件,在文件尾续写 | 建立新的文件 |
“rb+”(只读) | 为了读和写打开一个文件(二进制) | 出错 |
“wb+”(只读) | 为了读和写建立一个文件(二进制) | 建立新的文件 |
“a+”(只读) | 打开一个文件,在文件尾续写(二进制) | 建立新的文件 |
打开失败,返回NULL
int fclose(FILE*stream)
stream指向要关闭的流的FILE对象的指针
功能:关闭参数stream关联的文件,并取消其关联关系
失败会返回EOF(通常为-1)
要包含
功能:向文件或标准输出流输出字符
int fputc(int character,FILE*stream)(character是指被写入的字符,stream指向输出流)
返回值:失败时通常返回EOF(-1),错误指示器会被设置,可以通过ferror()检查错误
#include
int main()
{
FILE* fp = fopen("test.txt", "w");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
fputc('a', fp);
fputc('b', fp);
fclose(fp);
fp = NULL;
return 0;
}
int fgetc(FILE*stream)
功能:从参数stream指向的流中读写一个字符
返回值:成功时返回读取的字符(int)
若调用的时候已经处于文件末尾,返回EOF并设置流的文件结束指示器(feof),若发生错误,返回EOF并设置流的错误指示器(ferror)
#include
int main()
{
FILE* fp = fopen("text.txt", "r");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
int n=fgetc(fp);
printf("%c", n);
return 0;
}
int feof(FILE*stream)功能:检测stream指向的流是否到文件末尾
int ferror(FILE*stream)功能:检测stream指向的流是否发生读写错误
在读取文件的过程中,遇到文件末尾,文件读写就会结束,这时读取函数会在对应的流上设置一个文件结束指示符(可以被feof()检测到,设置了返回非0,未被设置返回0)
在读取文件的过程中,发生错误,文件读写就会结束,这时读取函数会在对应的流上设置一个文件错误指示符(可以被ferror()检测到,设置了返回非0,未被设置返回0)
#include
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
int n = fgetc(fp);
if (n == EOF)
{
if (feof(fp))
{
printf("遇到文件末尾了\n");
}
else
printf("读取发生了错误\n");
}
}
fclose(fp);
fp = NULL;
return 0;
}
向输出流中写入一个字符串
int fputs(const char*str,FILE*stream)
将str指向的字符串写到制定流中(不包含\0)
返回值:成功时返回非负整数,失败返回EOF,用ferror检查
#include
int main()
{
FILE* fp = fopen("test.txt", "w");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
fputs("sdhfh\0sdjfs", fp);
fclose(fp);
fp = NULL;
return 0;
}
char*fgets(char*str,int num,FILE*stream)
num是最大读取字符数,实际上最多读取num-1个字符;
成功时返回str指针
当遇到文件末尾时,返回NULL,feof()检测
当读取失败时,返回NULL,ferror()检测
#include
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
char arr[] = "*************";
fgets(arr, sizeof(arr), fp);
printf("%s", arr);
fclose(fp);
fp = NULL;
return 0;
}
int fprintf(FILE*stream,const char*format,...)format:格式化字符串,...是可变参数列表
功能:将格式化的数据写入指定的文件流
失败时先设置对应流的错误指示器,再返回负值
#include
struct s
{
char name[20];
int age;
double score;
};
int main()
{
struct s b = { "zhangsan",18,56.2f };
FILE*fp=fopen("test.txt", "w");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
fprintf(fp, "%s,%d,%lf", b.name, b.age, b.score);
fclose(fp);
fp = NULL;
return 0;
}
int fscanf(FILE*stream,const char*format,...)
功能:从指定文件流中读取格式化数据
返回值:成功时返回成功填充到参数列表的项数,可能少(格式,数据匹配失败,读取发生错误,到达末尾),在成功读取数据前失败时 错误返回EOF ferror(),末尾 返回EOF,feof()
#include
struct s
{
char name[20];
int age;
double score;
};
int main()
{
struct s b = { "zhangsan",18,56.2f };
FILE*fp=fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
fscanf(fp, "%s %d %lf", b.name, &(b.age), &(b.score));
fprintf(stdout, "%s,%d,%lf", b.name, b.age, b.score);
fclose(fp);
fp = NULL;
return 0;
}
size_t fread(void*ptr,size_t size,size_t count,FILE*stream)
从stream指向的文件流中读取数据块,并将其存到ptr指向的缓冲区
遇到文件末尾feof()
读取发生错误ferror()
#include
int main()
{
int data[5] = { 0 };
FILE* fp = fopen("data.bin", "rb");
if (fp == NULL)
{
perror("fopen");
return -1;
}
size_t num_read = fread(data, sizeof(int), 5, fp);
if (num_read != 5)
{
if (feof(fp))
printf("Reached end of file\n");
else if (ferror(fp))
printf("Error reading file\n");
}
else
{
for (int i = 0; i < 5; i++)
{
printf("Data[%d]: %d\n", i, data[i]);
}
}
// 关闭⽂件
fclose(fp);
return 0;
}
size_t fwrite(const void*ptr,size_t size,size_t count,FILE*stream) ptr数据块的指针,size每个数据项的大小,count数据项的大小
用于将数据块写入stream指向的文件流中,二进制
#include
int main()
{
int data[] = { 1, 2, 3, 4, 5 };
FILE* fp = fopen("data.bin", "wb");
if (fp == NULL)
{
perror("fopen");
return -1;
}
if (fwrite(data, sizeof(int), 5, fp) != 5)
{
perror("fwrite");
return -1;
}
fclose(fp);
fp= NULL;
return 0;
}
标准输出输入流的格式化输入输出函数(一般是到屏幕)
针对所有输入输出流的格式化的输入输出函数
输入输出流是数组
sprintf 将格式化的数据写入字符数组(字符串)
成功时返回写入buffer的值(不包含\0)
失败时返回负值
int sprintf(char* str,const char*format,...)
sscanf 从字符串读取格式化数据
成功时返回成功解析并赋值的参数数量,失败/未匹配:返回EOF
int sscanf(const char*str,const char*format,...)
根据文件指针的位置和偏移量来定位文件指针(文件内容的光标)
int fseek(FILE*stream,long int offset,int origin)
offset 偏移量 左负右正 origin SEEK_SET文件开始的位置 SEEK_COR文件指示器当前的位置 SEEK_END文件末尾
#include
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET);
fputs(" sam", pFile);
fclose(pFile);
return 0;
}
返回文件指针相对于起始位置的偏移量
long int ftell(FILE*stream)
#include
int main()
{
FILE* pFile;
long size;
pFile = fopen("myfile.txt", "rb");
if (pFile == NULL)
perror("Error opening file");
else
{
fseek(pFile, 0, SEEK_END);
size = ftell(pFile);
fclose(pFile);
printf("Size of myfile.txt: %ld bytes.\n", size);
}
return 0;
}
让文件指针返回到文件的起始位置
void rewind(FILE*stream)
rewind(pf)=fseek(pf,0,SEEK_SET)
#include
int main()
{
int n;
FILE* pFile;
char buffer[27];
pFile = fopen("myfile.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, pFile);
rewind(pFile);
fread(buffer, 1, 26, pFile);
fclose(pFile);
buffer[26] = '\0';
printf(buffer);
return 0;
}
文件缓冲区:系统自动的在内存中为每一个正在使用的文件开辟的一块文件缓冲区(提高效率)
缓冲:完全缓冲,行缓冲,无缓冲
功能:可以强制刷新参数stream制定的流的缓冲区,确保数据写入底层设备
int fflush(FILE*stream) 成功返回0,失败返回-1
输入流:将缓冲区未写入的数据立即写入文件
输出流:非C语言标准规定的行为
NULL:刷新所有打开的输出流
#include
#include
//VS2022 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;
}
ps
(
1.之队输出流/更新流(最后一次操作为输出)有明确刷新行为
2.输入流的刷新行为不可移植
3.程序正常终止(fclose)或调用fclose会自动刷新,程序崩溃时缓冲区数据可能丢失
)
行为 | “r+” | “w+” | “a+” |
解释 | 可读可写 | 可读可写 | 可读可写 |
文件不存在 | 打开失败 | 创建新的 | 创建新的 |
文件存在时 | 保留 | 清空 | 保留 |
初始文件指针的位置 | 开头 | 开头 |
末尾 |
写入是否覆盖 | 是(可定位) | 是(全部) | 否 |
用途 | 修改部分 | 创建新的覆盖旧的 | 追加 |
1.在写完后,要读的时候一定要用fflush()刷新缓冲区/fseek() rewind()重新定位文件指示器
2.在读完之后,在写之前用fseek(),rewind()重新定位指示器