为什么要使用文件
在程序运行的时候,所有的数据都是保存在内存中的,比如说变量,数组。而如果程序结束,这些数据就会消失,下一次打开程序就不会存在了。为了让数据能够持久化,我们可以让数据存在磁盘文件里。
把数据存到电脑的硬盘上,就能实现数据持久化。
什么是文件
程序的文件分为两大类:程序文件,数据文件
程序文件
程序文件包括源文件(.c),目标文件(windows系统下是.obj),可执行文件(windows环境下 .exe)等等
数据文件
被用来存放数据,一般程序读取或者写入数据的地方。
文件名
一个文件名要有唯一的文件表示:
文件路径+文件名主干+文件后缀
例如:c:\code\test.txt
文件的打开和使用
文件指针
文件本来在硬盘中,当我们要对文件进行操作的时候,先要打开文件。这里打开文件就是在内存区域创建一个临时空间,被称为文件信息区,在这个空间中放入文件的各种信息(路径,名字,状态),而这些信息是保存在一个结构体变量中的。这个结构体变量的类型是FILE。(这个结构体的声明是在头文件stdio里面的,结构体包含了文件的各种信息)。而指向这个空间的结构体指针,就是文件指针----FILE*pf。
(FILE这个类型是把结构体类型重命名后的,像这样 typedef struct A FILE ; ,在头文件里是这样重命名的)
所以在打开文件时,系统会自动创建一个空间,并把文件信息放到空间里的变量里。
我们可以创建这样一个指针,就能通过这个指针来找到与它关联的文件。
FILE * pf;
文件的打开和关闭
这里的打开文件不是简单的我们电脑上双击文件,然后把文件的页面打开。
打开文件后,会在内存中创建一个文件信息区,这个时候会返回一个FILE* 类型的文件信息区的指针,这样就能建立指针和文件信息区间的联系。
因为文件打开后会占用内存的空间, 所以对文件操作完之后,要关闭文件。
规定的文件打开和关闭函数是:fopen , fclose
fopen
FILE * fopen ( const char* filename , const char * mode )
两个参数,
第一个filename是文件的路径,别忘了用""
第二个mode是文件的打开方式(文件的使用方式),也要用""
fclose
int fclose ( FILE* stream);
stream就是要关闭的文件的指针 。
返回值:如果关闭成功了,返回0;如果关闭失败,返回EOF
文件的打开方式
如下
!!如果指定文件存在,那些要”建立一个新文件”的文件使用方式,会建一个新文件并覆盖掉原有的内容。!!
使用方式
int main()
{
FILE * pf;
pf = fopen("C:\code\test.txt","w");//以写的方式打开文件
if(pf == NULL)
{
perror("错误是:");
}
else
{
//文件操作.....
}
fclose(pf);//关闭文件
pf == NULL
return 0;
}
文件的顺序读写
什么是读/写,输入/输出
这些都是以内存为主体,要站在内存的角度思考。
键盘的信息,内存区读取,并且输入数据进来。
内存中的信息,写(输出)到屏幕,是输出数据出去。
对文件,把数据传给文件,是输出,写
内存从文件里拿数据,是输入,读 。
字符相关读写函数
fgetc
int fgetc ( FILE * stream )
读取目标地址的文件,按顺序读取文件里的内容
读取成功,返回文件指针正在指向的那个字符的ASCII码值。然后把指针向后移动一位(下一次调用就会读取下一个字符了)
如果读取失败,返回EOF
int ch = fgetc(pf);
printf("%c\n",ch);
fputc
int fputc ( int character, FILE * stream )
在文件指针处写下一个字符 character,一次写一个,按顺序一个一个写(是int因为传进来的是ASCII码值)
如果写文件失败,返回EOF。
fputc('a',pf);
文本行(字符串)相关读写函数
fgets
char * fgets ( char * str, int num, FILE * stream )
str读取到的字符串要放到内存中的位置的指针
num是读取并拷贝到str里的最大字符数。
char buf[20]={0};//创建一个用来存放读取到的内容的数组
fgets(buf,5,pf);//读取内容存放到buf内,读取5个字符
//实际上读4个,因为在buf内最后要放\0
//读完buf内有5个元素
printf("%s\n",buf);//打印buf
一次只能读一行,读取字符数超过文件中一行的字符数,超出的不会读到下一行,而是直接不读了。
fputs
int fputs ( const char * str, FILE * stream )
str指向字符串,stream指向文件
fputs("abcdef",pf);
在文件里写下abcdef
格式化的读写函数
fscanf
int fscanf ( FILE * stream, const char * format, ... );
从文件里输入信息到内存中
看看熟悉的scanf
int scanf ( const char * format, ... );
可以发现,fscanf 比 scanf就多了个文件指针。由此可以得出,fscanf和scanf的使用方法是一样的
scanf是从我们键盘输入的缓冲区读取数据放到内存中对应的地方,fscanf是从文件中读取数据放到内存中对应的地方
int a;
char b;
fscanf(pf,"%d %s",&a,&d);//把文件中的内容输入到a,b中
//可以把文件中的内容,看作是用scanf时,缓冲区的内容
fprintf
int fprintf ( FILE * stream, const char * format, ... );
同上,使用操作适合printf是一样的,只不过printf是把数据打印到屏幕上,而fprintf是把数据输入到文件里。
int a=5;
char b='m';
fprintf(pf,"%d,%s\n",a,b);
什么是流
在c语言中,数据传到输出设备时,不是直接传过去的,而是先传到 流 里,之后再由c语言封装好,传给设备。
流,是由FILE*的指针来管理的
任何一个c语言程序,在打开和运行时,默认会打开三个流:
stdin---标准输入(键盘) 类型:FILE*
stdout--标准输出(屏幕) FILE*
stderr--标准错误(屏幕) FILE*
所以像是printf,scanf能直接用,且起效果,不用我们自己去打开流。
而上面的每个函数,适用于每个输入/输出流
-->既可以从键盘获取数据,也可以从文件中获取/既可以输出到文件,也能输出到屏幕。
int main()
{
int ch = fgetc(stdin);//从键盘输入流获取
fputc(ch,stdout);//在屏幕上输出
return 0;
}
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = {0};
fscanf(stdin,"%s %d %f",s.name,&(s.age),&(s.score));//从键盘读
fprintf(stdout,"%s %d %f",s.mane,s.age,s.score);//输出到屏幕
return 0;
}
二进制输入输出函数
进行二进制的读写,在打开文件时要用二进制的形式打开,参考前面文件打开方式
fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
ptr,元素的地址
size,元素的大小
count,元素的个数
stream,写文件的地址指针
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s={"张三",20,98.5};
FILE *pf =fopen("test.txt","wb");//打开一个二进制文件
if(NULL==pf)
{
perror("fopen");
return 1;
}
//写文件
fwrite(&s, sizeof(struct S) , 1, pf);
//关闭文件
fclose(pf);
pf=NULL;
}
fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
ptr,用与存放读到的数据
size,读取数据元素的大小
count,元素个数
stream,读取地址
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s={0};
FILE *pf =fopen("test.txt","rb");//注意!!
//!!这里是以读的形式打开二进制文件
if(NULL==pf)
{
perror("fopen");
return 1;
}
//读文件
fread(&s, sizeof(struct S) , 1, pf);
printf(%s %d %f\n),s.name,s.age,s.score);
//关闭文件
fclose(pf);
pf=NULL;
}
类似的函数
sprintf---把一个格式化的数据,存放在字符串中。
sscanf--从一个字符串中还原出一个结构体(格式化的数据)。
int main()
{
struct S s = {"zhangsan", 20, 98.5};
char buf[100] = { 0 };
sprintf(buf, "%s %d %f", s.name, s.age, s.score);
printf("%s\n", buf);//按照字符串打印的
struct S tmp = { 0 };
sscanf(buf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
printf("%s %d %f\n", tmp.name, tmp.age, tmp.score);//打印结构体数据
return 0;
}
文件的随机读写
前面的是文件的顺序读写,只能按顺序下去读或写,文件的随机读写就是能从文件的任何地方开始读或写。
fseek
int fseek(FILE * stream , long int offset,int origin);
根据文件指针位置和所给的偏移量来找到读写位置。
offset是偏移量
origin:1.SEEK_SET---文件指针起始位置
2.SEEK_CUR---文件指针当前位置
3.SEEK_END---文件指针结尾位置
fseek只要作用,就是调整文件指针
fseek( pf , 1 , SEEK_CUR);
就是把指针调整到当前位置再往后便宜一个位置。
ftell
long int ftell(FILE * stream);
得到当前位置相对于起始位置的偏移量
ftell(pf);
rewind
void rewind(FILE*stream);
让文件指针回到文件起始位置。
rewind(pf);
使用实例
//假定test.txt的内容是:abcdef
int main()
{
FILE* pf = fopen("test.txt","r");
if (pf == NULL)
{
perror("fopen");
}
else
{
int ch = fgetc(pf);
printf("%c\n", ch);//a,读取一次后,光标往后移动一位,指针向后移动一位
ch = fgetc(pf);//第二次读取时光标在第二位
printf("%c\n", ch);//b
fseek(pf, -1, SEEK_CUR);//调整光标位置,SEEK_CUR是当前的指向,是c
ch = fgetc(pf);
printf("%c\n", ch);
printf("%d\n", ftell(pf));//2
rewind(pf);//文件指针位置回到初始位置
//必须要回到初始位置才能关闭文件,不然找不到要关闭文件的位置
ch = fgetc(pf);
printf("%c\n", ch);
}
return 0;
文本文件和二进制文件
根据数据的组织形式,分为文本文件和二进制文件
假设有一个10000要存到磁盘中
文件读取结束的判定
feof/ferror
feof(pf)
ferror(pf)
在文件读取的过程中,不能用feof函数的返回值来直接判断文件是否结束,feof是用来判断文件读取结束的原因--->是不是因遇到eof才结束的(EOF是文件结束的标志)
正确流程是:
首先文件读取结束了
结束后想知道读取结果:
feof(pf)---返回值为真,就说明是文件正常读取遇到了结束标志而结束的
ferror(pf)---返回值为真,就说明是文件在读取过程中出错了而结束的。