Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1676219
  • 博文数量: 311
  • 博客积分: 7778
  • 博客等级: 少将
  • 技术积分: 4186
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-09 19:59
个人简介

蓝点工坊(http://www.bluedrum.cn) 创始人,App和嵌入式产品开发。同时也做相应培训和外包工作。 详细介绍 http://pan.baidu.com/s/1y2g88

文章存档

2012年(3)

2011年(115)

2010年(170)

2009年(23)

分类: C/C++

2010-09-28 15:39:37

标准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:fputcfgetc

l         fputc

    函数原型 :int  fputc(int c, FILE *fp)
    功能:把一字节代码c写入fp指向的文件中
    返值:正常,返回c;出错,为EOF

l         fgetc

函数原型:int  fgetc(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 <stdio.h>

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()用来侦测是否读取到了文件尾,尾数streamfopen()所返回之文件指针。如果已到文件尾则返回非零值,其他情况返回0。但是在读写框架使用feof往往出错。在实际编程是用不上feof的,因此我要求学员不要使用这个函数。

下面就是因为使用feof产生错误的实例。忘掉它吧,没有什么功能是必须使用feof的

/* ch12_2.c*/
/* Program to create backup of a file */

#include <stdio.h>

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_t  fread(void  *buffer,size_t  size, size_t  count,FILE   *fp)

size_t  fwrite(void  *buffer,size_t  size, size_t  count,FILE   *fp)

l       功能:读/写数据块

l       返值:成功,返回读/写的块数;出错或文件尾,返回0

l       说明:

Ø         typedef   unsigned   size_t;

Ø         buffer:  指向要输入/输出数据块的首地址的指针

Ø         size:  每个要读/写的数据块的大小(字节数)

Ø         count:   要读/写的数据块的个数

Ø         fp:    要读/写的文件指针

Ø         freadfwrite 一般用于二进制文件的输入/输出

注意这里参数有两个特别的地方,FILE * 位于最后位置,读写的缓冲区的的大小是两个参数.size和count.

 把读写总尺寸分成size和count,主要是方便struct 连续读写
  每次读/写的总尺寸是size*count,如果成功,返回是块数

读写测试样例:读写连续结构样例

从键盘输入4个学生数据,把他们转存到磁盘文件中去

#include <stdio.h>
#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<SIZE;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<SIZE;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<SIZE;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 <stdio.h>
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 <stdio.h>

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

*             函数原型:

int  fprintf(FILE  *fp,const char  *format[,argument,…])

int  fscanf(FILE  *fp,const char  *format[,address,…])

*           功能:按格式对文件进行I/O操作

*           返值:成功,返回I/O的个数;出错或文件尾,返回EOF

 

文件的定位:rewind()

      几个概念

l    文件位置指针----- 指向当前读写位置的指针

l    读写方式

     顺序读写:位置指针按字节位置顺序移动,叫~
     随机读写:位置指针按需要移动到任意位置,叫~
 

 

文件的定位:fseek

     fseek 函数

l    函数原型:  int  fseek(FILE  *fp,long  offset,int whence)

l    功能:改变文件位置指针的位置

l    返值:成功,返回0 ;失败,返回非0 值

 

offset取值

位移量(以起始点为基点,移动的字节数)

>0    向后移动

<0    向前移动

 

whence

起始点

文件开始              SEEK_SET     0

文件当前位置      SEEK_CUR    1

文件末尾              SEEK_END    2

 

    样例:1) 欲将读写位置移动到文件开头时:fseek(FILE *stream,0,SEEK_SET);
2)
欲将读写位置移动到文件尾时:fseek(FILE *stream,0,0SEEK_END);

 

l         ftell函数

        函数原型:  long  ftell(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"));
}


阅读(2870) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~