标准C文件处理
Andrew Huang
开发中文件的重要情况不要多说。几乎所有实用程序用的带文件处理。比如制图软件就是对图片文件处理,游戏的几乎所有数据都由文件处理。
文件处理有两类,一类文本,内容是,另一类是二进制文件,两者在存储的本质上一样的,只是在程序处理有细微的差别。
每个操作系统都自己处理文件的专用的API,但是标准C制定一套标准文件处理接口,方便易用。只要是对普能文件操作,我要求学生一定要用标准C文件处理接口来处理文件。
标准C的处理,缓冲式处理文件:
一.操作文件要点:
-------------------------------------
在标准C文件处理函数中,所有打开的文件都由一个名为FILE 结构来描述.
缓冲文件系统为每个正使用的文件用FILE *在内存开辟文件信息区
文件信息用系统定义的名为FILE 的结构体描述
FILE 定义在stdio.h 中
文件使用前,必要打开.文件修改后,需要关闭文件才会把内容保存在文件系统中。
文件打开 时,系统自动 建立文件结构体,并把指向它的FILE *指针返回来,程序通过这个指针获得 文件信息,
文件关闭 后,它的文件结构体被释放.如果没有关闭,在退出程序时,操作系统会自动关闭打开文件。
文件使用方式:打开文件-->文件读/写-->关闭文件
文件的打开与关闭 C 文件操作用库函数 实现, 包含在stdio.h
打开文件fopen
函数原型: FILE *fopen(char *name,char *mode)
功能:按指定方式打开文件
返值:正常打开,为指向文件结构体的指针;打开失败,为 NULL
name是文件路径名,可以是绝对路径和相对路径,我建议用相对路径,并且用/来分隔,这样可以保证路径在各个操作系统的移植性.
mode是用来描述这个文件在随后如何操作的模式。它是一个字符串,可以做如下取值.
"r" 只读文件,
"w" 可读可写文件,如果文件不存在,将会自动创建,如果存在,则会被清空内容
"a" 可读可写文件,如果文件不存在,将会自动创建,如果存在,则读写指针指向文件尾。
"r+"表示打开只读文件,而且文件必须存在
"w+" 等同于"w"
"a+" 等同于"a"
b表示打开一个二进制文件,但现在已经不是必须的。(即有不有一个样),但为了有良好的风格,在打开一个二进制文件还是加上b
比如 "rb+","wb"
文件关闭fclose
作用: 使文件指针变量与文件脱钩 ,释放文件结构体和文件指针
函数原型:int fclose(FILE *fp)// 文件打开时返回的文件类型指针
功能:关闭fp指向的文件
返值:正常关闭为0;出错时,非0
三个特殊的文件
l 每个程序运行后, 三个特殊文件会被自动打开, 分别是标准输出, 标准输入和标准错
误输出 ,它不需要用fopen来打开即可直接使用
l 三个FILE * 全局变量在stdio.h 被声明. 因此程序只要包含stdio.h, 可以不需要打
开直接使用这个三个文件结构描述符
– 分别是 stdout,stdin,stderr.
– 可以用文件处理函数对stdout,stderr 进行写入, 相当于向屏幕打印字符.
– 可以用文件处理函数对stdin 进行读入, 相当接收键盘输入
– 即出程序时, 也不需要关闭这个三个FILE * 结构
比如我们习惯的
fprintf(stderr,"this is a error message\n");
文件打开和关闭的框架
要点,一定要对fopen结果做NULL检测!,文件打开失败的情况太多了
#define PRINT_INTX(e) printf("%s=0x%X\n",#e,e)
#define PRINT_INT(e) printf("%s=%d\n",#e,e)
void test1()
{
FILE * fp = NULL;
fp = fopen("test.txt","w+");
if(fp == NULL)
{
fprintf(stderr,"open file failure,errno=%d\n",errno);
return ;
}
PRINT_INTX(fp);
fclose(fp); //关闭文件
}
注意一个常见的错误,这个是操作符优先级=高于==,下面的表达式
if(fp = fopen("test22.txt","w+") == NULL)
实际是
if((fp = fopen("test22.txt","w+")) == NULL).
正确的写法是
if((fp = fopen("test22.txt","w+")) == NULL)
void test2()
{
FILE * fp = NULL;// if(fp = fopen("test22.txt","w+") == NULL) 等于 if(fp = (fopen("test22.txt","w") == NULL))
if((fp = fopen("test22.txt","w+")) == NULL)
{
fprintf(stderr,"open file failure,errno=%d\n",errno);
return ;
}
PRINT_INTX(fp);
fclose(fp);
}
二.文件读写操作
-------------------------------------------------------------
文件读写有一个隐念的概念, 文件位置指针----- 指向当前读写位置的指针 ,它保存在内存当中。当用w,r打开文件时,指针会指向文件开始,用a模式打开,会指向文件结束
文件读写可以字符或字符串,二进制buffer等来读写,每次读写成功后,内核会把读写指针自动移到下一个位置下。
按字符来读写fputc/fgetc
–字符I/O:fputc与fgetc
lfputc
–函数原型 :intfputc(int c, FILE *fp)
–功能:把一字节代码c写入fp指向的文件中
–返值:正常,返回c;出错,为EOF
lfgetc
–函数原型:intfgetc(FILE *fp)
–功能:从fp指向的文件中读取一字节代码
返值:正常,返回读到的代码值;读到文件尾或出错,为EOF
EOF是一个整数常量,值为-1,
注意这里是用int来存取字符,这个值保存是字符的ASCII码值。
void test3()
{
FILE * fp = NULL;//char * p = malloc(10);
fp = fopen("test.txt","w+");
if(fp == NULL)
{
fprintf(stderr,"open file failure,errno=%d\n",errno);
return ;
}
fputc('A',fp);
fputc('B',fp);
fputc('C',fp);
fputc('\n',fp);
fputs("hello!\n",fp);
fputs("hi!\n",fp);
fclose(fp);
}
思考,一个文件打开两次会有情况发生?这是初学常见的问题,参考这个代码
#include
int twice_open(char * file_name)
{
FILE * src_fp = NULL,*dst_fp = NULL;
char buffer[16];
int len;
src_fp = fopen(file_name,"w+");
if(src_fp == NULL)
{
fprintf(stderr,"open file %s failure \n",file_name);
return -1;
}
//以只读的方式打开一个文本文件
dst_fp = fopen(file_name,"w");
if(dst_fp == NULL)
{
fprintf(stderr,"create destion file %s failure \n",file_name);
return -1;
}
fputs("0123456789",src_fp);
fputs("abc",dst_fp);
fclose(dst_fp);
fclose(src_fp);
return 0;
}
void test1()
{
twice_open("4.txt");
}
int main()
{
test1();
}
结果就是内容互相覆盖。fputs(将一指定的字符串写入文件内)
int fputs(const char * s,FILE * stream);
fputs()用来将参数s所指的字符串写入到参数stream所指的文件内。
返回值 若成功则返回写出的字符个数,返回EOF则表示有错误发生。
fgets(由文件中读取一字符串)
char * fgets(char * s,int size,FILE * stream);
fgets()用来从参数stream所指的文件内读入字符并存到参数s所指的内存空间,直到出现换行字符、读到文件尾或是已读了size-1个字符为止,最后会加上NULL作为字符串结束。
这通常要求学生必须记住下面框架,这是使用频率最高的结构。其它程序也是防照这个框架改写
打印文本文件内容
int type_file2(char * filename)
{
FILE * fp = NULL;
char buf[1024];
fp = fopen(filename,"r");
if(fp == NULL)
{
fprintf(stderr,"open file %s failure\n",filename);
return -1;
}
while(fgets(buf,sizeof(buf),fp) != NULL)
{
//putchar(ch);
printf(buf);
}
fclose(fp);
}
注意常见的问题,
feof是检查文件流是否读到了文件尾
int feof(FILE * stream);
feof()用来侦测是否读取到了文件尾,尾数stream为fopen()所返回之文件指针。如果已到文件尾则返回非零值,其他情况返回0。但是在读写框架使用feof往往出错。在实际编程是用不上feof的,因此我要求学员不要使用这个函数。
下面就是因为使用feof产生错误的实例。忘掉它吧,没有什么功能是必须使用feof的
/* ch12_2.c*/
/* Program to create backup of a file */
#include
main()
{
FILE *in, *out;
char ch,infile[10],outfile[10];
printf("Please enter the infile name:\n");
scanf("%s",infile);
printf("Please enter the outfile name:\n");
scanf("%s",outfile);
if ((in = fopen(infile, "r"))== NULL)
{
printf("Cannot open infile.\n");
exit(0);
}
if ((out = fopen(outfile, "w"))== NULL)
{
printf("Cannot open outfile.\n");
exit(0);
}
while (!feof(in)) //这将造成多写一字符到目标文件
fputc(fgetc(in), out);
fclose(in);
fclose(out);
}
思考,如果文本文件一行数据超过过fgets给的buffer的长度,会有什么后果?
请写程序测试一下
fgets应用实例:安全从键盘输入字符串的代码
void test7()
{
char buffer[128];
fputs("input a number:",stdout);
fgets(buffer,sizeof(buffer),stdin);//
printf("your input number %d\n",atoi(buffer));// snprintf(buffer,sizeof(buffer),"%d",100);
}
二进制数据读写
l函数原型:
size_tfread(void*buffer,size_tsize, size_tcount,FILE*fp)
size_tfwrite(void*buffer,size_tsize, size_tcount,FILE*fp)
l功能:读/写数据块
l返值:成功,返回读/写的块数;出错或文件尾,返回0
l说明:
Øtypedefunsignedsize_t;
Øbuffer:指向要输入/输出数据块的首地址的指针
Øsize:每个要读/写的数据块的大小(字节数)
Øcount:要读/写的数据块的个数
Øfp:要读/写的文件指针
Øfread与fwrite一般用于二进制文件的输入/输出
注意这里参数有两个特别的地方,FILE * 位于最后位置,读写的缓冲区的的大小是两个参数.size和count.
把读写总尺寸分成size和count,主要是方便struct 连续读写
每次读/写的总尺寸是size*count,如果成功,返回是块数
读写测试样例:读写连续结构样例
从键盘输入4个学生数据,把他们转存到磁盘文件中去
#include
#define SIZE 2
struct student_type
{ char name[10];
int num;
int age;
char addr[15];
}stud[SIZE];
void save();
void display();
int main()
{
int i;
for(i=0;i
scanf("%s%d%d%s",stud[i].name,&stud[i].num,
&stud[i].age,stud[i].addr);
save();
display();
}
void save()
{ FILE *fp;
int i;
if((fp=fopen("d:\\fengyi\\exe\\stu_dat","wb"))==NULL)
{ printf("cannot open file\n");
return;
}
for(i=0;i
if(fwrite(&stud[i],sizeof(struct student_type),1,fp)!=1)
printf("file write error\n");
fclose(fp);
}
void display()
{ FILE *fp;
int i;
if((fp=fopen("d:\\fengyi\\exe\\stu_dat","rb"))==NULL)
{ printf("cannot open file\n");
return;
}
for(i=0;i
{ fread(&stud[i],sizeof(struct student_type),1,fp);
printf("%-10s %4d %4d %-15s\n",stud[i].name,
stud[i].num,stud[i].age,stud[i].addr);
}
fclose(fp);
}
l对于一个文件读入,读入数据不足一个块算一个块.因此很容易发生下列错误
#include
main()
{ FILE * file;
char buffer[512];
file = fopen(“example.ini”,”r”);
while(fread(buffer,sizeof(buffer),1,file)>0) //无法判断最后一块的尺寸
//while(fread(buffer,1,sizeof(buffer),file)>0) //正确.
{
...
}
fclose(file);
}
正确代码是把size设为1,
fread/fwrite样例:文件拷贝
#include
int copy_file(const char * src_name,const char * dst_name)
{
FILE * src = NULL,* dst = NULL;
char buffer[1024];
int len,cnt = 0;
if((src = fopen(src_name,"rb")) == NULL)
{
fprintf(stderr,"open source file %s failure\n",src_name);
return -1;
}
if((dst = fopen(dst_name,"wb+")) == NULL)
{
fclose(src);
fprintf(stderr,"create destion file %s failure\n",dst_name);
return -2;
}
#if 1
while((len = fread(buffer,1,sizeof(buffer),src))>0)
{
cnt+= len;
fwrite(buffer,1,len,dst);
}
#endif
#if 0
//注意这样写是错误写法,这样造成最一段数据尺寸有错
while((len = fread(buffer,sizeof(buffer),1,src))>0)
{
cnt+= len;
fwrite(buffer,sizeof(buffer),len,dst);
}
#endif
fclose(dst);
fclose(src);
printf("copy file %s to %s success,bytes is %d\n",src_name,dst_name,cnt);
return 0;
}
int main()
{
copy_file("1.pdf","11.pdf");
}
fflush: 不关闭文件时保存文件
l 一般情况下只要关闭文件, 就可以把内存内容保存文件中.
l 但很多情况下是不能关闭文件的, 如服务器的日志文件, 但可以需要定时保存以防断
电丢失数据.
l fflush 就是无需关闭文件, 强制更新文件.
int fflush(FILE* stream);
格式化I/O:fprintf与fscanf
*函数原型:
intfprintf(FILE*fp,const char*format[,argument,…])
intfscanf(FILE*fp,const char*format[,address,…])
*功能:按格式对文件进行I/O操作
*返值:成功,返回I/O的个数;出错或文件尾,返回EOF
文件的定位:rewind()
–几个概念
l文件位置指针-----指向当前读写位置的指针
l读写方式
–顺序读写:位置指针按字节位置顺序移动,叫~
–随机读写:位置指针按需要移动到任意位置,叫~
文件的定位:fseek
–fseek函数
l函数原型:intfseek(FILE*fp,longoffset,int whence)
l功能:改变文件位置指针的位置
l返值:成功,返回0 ;失败,返回非0 值
offset取值
位移量(以起始点为基点,移动的字节数)
>0向后移动
<0向前移动
whence
起始点
文件开始SEEK_SET0
文件当前位置SEEK_CUR1
文件末尾SEEK_END2
样例:1)欲将读写位置移动到文件开头时:fseek(FILE *stream,0,SEEK_SET);
2)欲将读写位置移动到文件尾时:fseek(FILE *stream,0,0SEEK_END);
lftell函数
–函数原型:longftell(FILE*fp)
–功能:返回位置指针当前位置(用相对文件开头的位移量表示)
–返值:成功,返回当前位置指针位置;失败,返回-1L,
一种求文件大小尺寸代码
long file_size(char * filename)
{
FILE * fp = NULL;
long len;
fp = fopen(filename,"rb");
if(fp == NULL)
return -1;
if(fseek(fp,0,SEEK_END)== -1)
return -2;
len = ftell(fp);
fclose(fp);
return len;
}
void test13()
{
PRINT_INT(file_size("1.pdf"));
}