文章目录
1.为什么使用文件
我们写的程序数据存储在电脑的内存里,如果没有文件,那么随着电脑关机内存释放,数据也就一去不复返了,所以需要文件来持久化的保存数据。
2.什么是文件
磁盘/硬盘上的文件叫文件,按功能分两种,程序文件和数据文件
2.1程序文件
程序文件包含源文件(后缀.c),目标文件(Windows环境后缀.obj),可执行程序(windows后缀.exe)。
2.2数据文件
文件的内容不一定是程序,而是程序运行时读取的数据。
我们有时候会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存使用,这里处理的就是磁盘上文件
2.3文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含三部分:文件路径+文件名主干+文件后缀
如:c:\code\test.txt
为了方便起见,文件标识常被称为文件名
3.二进制文件和文本文件
根据数据的组织形式,数据文件被称为文本文件和二进制文件
数据在内存以二进制形式存储,不加转换的输出到外存的文件就是二进制文件
在外存上以ASCII码的形式存储,则需要在存储上转换,以ASCII字符的形式存储的文件就是文本文件。
⼀个数据在⽂件中是怎么存储的呢?
字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使⽤⼆进制形式存储
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽⼆进制形式输出,则在磁盘上只占4个字节。
代码展示:
#include <stdio.h>
int main()
{
int a = 10000000;
FILE* pf=fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
vs打开二进制文件步骤
10000000在二进制文件中
这类就叫文本文件,直接就能看懂
4.文件的打开和关闭
4.1流和标准流
4.1.1 流
我们程序的数据需要输出到各种外部设备,也需要从外界获取数据,不同外设输入输出操作不同,为了方便程序员对外设操作,我们抽象出流的概念,我们可以把流想象成流淌着字符的河。
C程序针对文件,画面,键盘的数据输入输出都是通过流操作完成的
一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作
4.1.2标准流
为什么我们从键盘输入数据,向屏幕输出数据,并没有打开流呢
那是因为C语言在启动时,默认打开了三个流
- stdin-标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据
- stdout- 标准输出流,在大多数环境中输出至显示屏,printf函数就是把数据输出到标准输出流
- stderr-标准错误流,在大多数环境中输出至显示屏界面。
这是默认打开了这三个流,我们使用scanf和printf就是可以直接进行输入输出操作
stdin,stdout,stderr 三个流类型是FILE *,通常称为文件指针
C语言就是通过FILE *的文件指针来维护流的各种操作的
4.2文件指针
前⾯的内容中提到了FILE* 的指针类型,我们称为“⽂件类型指针”,简称“⽂件指针”。每个被
使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名字,⽂
件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明
的,取名FILE
stdio头⽂件中有以下的⽂件类型申明
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.3文件的打开和关闭
文件在读写之前应该先打开文件,使用结束应该关闭文件
打开文件的同时都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系
FILE* fopen(const char* filename, const char* mode);//打开文件
int fclose(const char* stream);//关闭文件
#include <stdio.h>
int main()
{
FILE* pf = fopen("date.txt", "w");
FILE* pp = fopen("C:\\Users\\yy\\Desktop\\cod.txt", "w");
//FILE* pf = fopen(".//..//..//..//game//date.txt","w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
else if (pp == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
fclose(pp);
pf = NULL;
pp = NULL;
return 0;
}
用w可以创建文件,如果原先没有文件,用r会报错
在打开⽂件的时候,⽂件名字中可以加⼊⽂件所在的路径,可以是相对路径,也可以是绝对路径
5.文件的顺序读写
5.1顺序读写函数介绍
int fputc(int c, FILE* stream)
#include <stdio.h>
int main()
{
FILE* pf = fopen("date.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
fputc('c', pf);
fclose(pf);
pf=NULL;
return 0;
}
#include <stdio.h>
int main()
{
FILE* pf = fopen("date.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch;
for (ch = 'A';ch <= 'Z';ch++)
{
if (fputc(ch, pf) == EOF)
{
perror("Error writing to file");
return -1;
}
}
fclose(pf);
pf=NULL;
return 0;
}
int fgetc ( FILE * stream );
#include <stdio.h>
int main()
{
FILE* pf = fopen("date.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c\n", ch);
}
fclose(pf);
pf = NULL;
return 0;
}
当 fgetc 的参数是stdin 的时候,函数是在标准输⼊(键盘)上读取字符。
当 fpuct的参数是stdout 的时候,函数是向标准输出(屏幕)上写字符
int ch = getc(stdin);
putc(ch,stdout);
5.2对比一组函数
fputs
输入一行,fgets读取一行
打开文件,文件如果存在,写入会清空原来文件的内容
int fputs(const char *str, FILE *stream);
#include <stdio.h>
int main()
{
FILE* pf = fopen("date.txt", "w");
if (pf == NULL)
{
perror("fopen");
}
const char s[100] = "i like c";
fputs(s,pf);
fputs("hello world", pf);
fclose(pf);
pf = NULL;
return 0;
}
没有重新打开文件,所以不会清除i like c,并且输入在同一行,如果想换行,加\n
const char s[100] = “i like c\n”;
fgets读取一行,使用时记得改为“r”
char* fgets(char* str, int n, FILE * stream)
#include <stdio.h>
int main()
{
FILE* pf = fopen("date.txt", "r");
if (pf == NULL)
{
perror("fopen");
}
char string[100] = { 0 };
while (fgets(string, 255, pf) != NULL)//读取每一行并打印
{
printf("%s", string);
}
fclose(pf);
pf = NULL;
return 0;
}
fgets使用时中间的数要开大一些,比如有10个数,n=11,前10位读取,最后1位读取\n,
类似fgetc,fputc所有输入输出流
#include <stdio.h>
int main()
{
char arr[100];
fgets(arr, 101, stdin);
fputs(arr, stdout);
return 0;
}
fscanf
fscanf()函数⽤于从指定流中读取格式化输⼊,并根据指定的格式化字符串将输⼊数据解析为相应的数
据类型。
int fscanf(FILE *stream, const char *format, …)
与scanf不同,fscanf从指定的的流中读取数据,可以是标准输入流,也可以是文件流
示例:
假设有⼀个包含⼀些数据的⽂件"data.txt",内容如下:
John 25 1.7
Emily 23 1.6
zhangsan 28 2.1
#include <stdio.h>
int main()
{
char name[20];
int age;
float height;
FILE* pf = fopen("date.txt", "r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
while (fscanf(pf,"%s %d %f",name,&age,&height)!=EOF)
{
printf("%s %d %.1f\n", name, age, height);
}
fclose(pf);
pf != NULL;
return 0;
}
fprintf()
int fprintf(FILE *stream, const char *format, …)
将输出写⼊到指定的件流中,可以是标准输出流,也可以是⽂件流
#include <stdio.h>
int main()
{
char name[20]="zhangsan";
int age=18;
float height=1.7;
FILE* pf = fopen("date.txt", "w");
if (pf == NULL)
{
perror("fopen");
return -1;
}
fprintf(pf, "%s %d %f", name, age, height);
fclose(pf);
pf != NULL;
return 0;
}
fwrite
功能:函数⽤于将数据块写⼊⽂件流中,是以2进制的形式写⼊的
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr:指向要写⼊的数据块的指针
size:要写⼊的每个数据项的⼤⼩(以字节为单位)
nmemb :要写⼊的数据项的数量
返回值:返回实际写⼊的数据项数量
#include <stdio.h>
int main()
{
int ptr[5] = { 1,2,3,4,5 };
FILE* pf;
pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return -1;
}
if (fwrite(ptr, sizeof(int), 5, pf) != 5)
{
perror("fwrite");
return -1;
}
fclose(pf);
pf = NULL;
return 0;
}
fread
功能:函数⽤于从⽂件中读取数据块,并将其存储到内存缓冲区中
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "rb");
if (fp == NULL)
{
perror("fopen");
return -1;
}
int arr[5];
size_t num=fread(arr, sizeof(int), 5, fp);
//检查是否读取成功
if (num != 5)
{
if (feof(fp))
{
perror("Reached end of file\n");
}
else if (ferror(fp))
{
perror("Error reading file\n");
}
}
else
{
for (int i = 0;i < 5;i++)
{
printf("arr[%d]:%d\n",i,arr[i]);
}
}
fclose(fp);
fp = NULL;
return 0;
}
sprintf
将带格式的数据转换字符串形式
#include <stdio.h>
int main()
{
char name[20] = "zhangsan";
int age = 18;
double height = 1.8;
char arr[150] = { 0 };
sprintf(arr, "%s %d %f", name, age, height);
printf("%s", arr);
return 0;
}
sscanf
把字符串中的数据按照格式 提取格式化数据
#include <stdio.h>
int main()
{
char name[20] = "zhangsan";
int age = 18;
double height = 1.8;
char arr[150] = { 0 };
sprintf(arr, "%s %d %.1f", name, age, height);
printf("%s\n", arr);
char name2[20] = "";
int age2 = 0;
double height2 = 0;
sscanf(arr, "%s %d %f", name2, &age, &height);
printf("%s\n", name2);
printf("%d\n", age);
printf("%.1f\n", height);
return 0;
}
6.文件的随机读写
6.1freek
功能:函数⽤于设置⽂件流的位置指针,即将⽂件流的当前位置移动到指定位置。这个函数通常与ftell() ⼀起使⽤,可以在⽂件中定位到特定的位置进⾏读取或写⼊操作。
int fseek(FILE * stream, long offset, int origin);
stream :指向FILE类型结构体的指针,指定了要设置位置指针的⽂件流
offset :相对于origin 参数的偏移量,以字节为单位
origin :指定偏移量的起始位置,可以是以下值之⼀:
SEEK_SET
:从⽂件起始位置开始偏移。
SEEK_CUR
:从当前位置开始偏移。
SEEK_END
:从⽂件末尾位置开始偏移
返回类型:
int :如果成功设置位置指针,则返回0;如果发⽣错误,返回⾮零值
#include <stdio.h>
int main()
{
int position=0;
FILE* pf = fopen("date.txt", "r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
if (fseek(pf,-4,SEEK_END)!= 0)
{
perror("fseek");
return -1;
}
position = ftell(pf);
if (position == -1)
{
perror("ftell");
return -1;
}
fclose(pf);
pf=NULL;
printf("Current position :%d", position);
return 0;
}
6.2ftell
long ftell(FILE *stream);
功能:函数⽤于获取⽂件流的当前位置,即指针相对于⽂件起始位置的偏移量(以字节为单位)
返回值:long :返回当前位置相对于⽂件起始位置的偏移量(以字节为单位)。如果发⽣错误,返回
(-1)
#include <stdio.h>
int main()
{
FILE* pf;
pf = fopen("date.txt", "r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
int position = ftell(pf);
if (position == -1)
{
perror("ftell");
return -1;
}
printf("current position %d\n", position);
fgetc(pf);
position = ftell(pf);
//读取一个字符,光标向后偏移一位
printf("current position %d\n", position);
fclose(pf);
pf = NULL;
return 0;
}
6.3rewind
让文件指针回到起始位置
void rewind(FILE* stream);
7.文件读取结束的标志
7.1被错误使用的feof
feof不直接用于判断文件是否读取结束,而是读取结束后,判断是否遇到了文件末尾。因为读取结束有可能遇到了其他的错误。
如果文件到了末尾,返回非0值,否则返回0
#include <stdio.h>
int main()
{
FILE* fp = fopen("date.txt", "r");
if (fp == NULL)
{
perror("fopen");
return -1;
}
int ch;
while (ch = fgetc(fp) != EOF)
{
printf("%d\n", ch);
}
if (feof(fp))
{
printf("文件遇到末尾,无错误\n");
}
else
{
perror("文件读取发生其他错误\n");
}
fclose(fp);
fp = NULL;
return 0;
}
perror
int ferror(FILE *stream)
功能:函数⽤于检查⽂件流的错误标志,以确定⽂件读写操作是否发⽣了错误。
返回值:如果⽂件流的错误标志已经设置,则返回⾮零值;否则返回0
#include <stdio.h>
int main()
{
FILE* fp = fopen("date.txt", "w");
if (fp == NULL)
{
perror("fopen");
return -1;
}
fputs("abc", fp);
if (ferror(fp))
{
printf("读写错误\n");
}
fclose(fp);
fp = NULL;
return 0;
}