zoukankan      html  css  js  c++  java
  • 基于函数的I/O操作(头文件stdio.h)

    基于函数库的I/O是C语言标准库的功能,基于系统级I/O函数实现。
    系统级I/O函数对文件的标识是文件描述符,C语言标准库中对文件的标识是指向FILE结构的指针。在头文件cstdio或stdio.h中声明了FILE结构,并对其他与标准I/O有关的常量、数据结构、函数等进行了定义。
    文件是用一个指向特定结构的指针来标识的,这个特定结构就是FILE结构,它描述了包含文件描述符在内的一组信息。即,它将打开文件抽象为一个类型为FILE的“流”,它是对文件描述符和流缓冲区的抽象。
    FILE结构体中的文件描述符指向进程级打开文件表,然后通过进程级打开文件表可以找到系统级打开文件表,进而可以通过文件控制块(FCB)操作物理磁盘上面的文件(对物理磁盘上的文件的操作实际是对其在内存的缓冲区的操作)。
     
    不同编译器的头文件stdio.h对于FILE结构定义略有差异,例如在VC6中有以下定义:
    #ifndef _FILE_DEFINED
    struct _iobuf {
        char *_ptr; //文件输入的下一个位置
        int _cnt; //当前缓冲区的相对位置
        char *_base; //指基础位置(即是文件的其始位置) 
        int _flag; //文件标志
        int _file; //文件描述符fd
        int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取
        int _bufsiz; //文件缓冲区大小
        char *_tmpfname; //临时文件名
    };
    typedef struct _iobuf FILE;
    #define _FILE_DEFINED
    #endif
    下面以摘抄自K&R C语言程序设计上的代码为例说明:
    #define NULL 0
    #define EOF (-1)
    #define BUFSIZ 1024
    #define OPEN_MAX  20   /* 最多打开文件数 */
    typedef struct _iobuf {
        int cnt;     /*未读写字节数 */
        char *ptr;   /*下一可读写位置 */
        char *base;  /* 起始位置 */
        int flag;    /* 存取模式 */ 
        int fd;      /*文件描述符 */
    }FILE;             
    extern  FILE  _iob[OPEN_MAX];      //用数组实现文件缓冲
    #define stdin (&_iob[0]) 
    #define stdout (&_iob[1])  
    #define stderr (&_iob[2])
     
    FILE _iob[OPEN_MAX]={
    { 0, ( char * ) 0, ( char * ) 0, _READ, 0 },      //标准输入
    { 0, ( char * ) 0, ( char * ) 0, _WRITE, 1 },     //标准输出
    { 0, ( char * ) 0, ( char * ) 0, _WRITE | _UNBUF, 2 },   //标准错误 
    };
     
    enum  _flags {
        _READ= 01,    /* file open for reading */
        _WRITE= 02,   /* file open for writing */
        _UNBUF= 04,  /* file is unbuffered */
        _EOF= 010,    /* EOF has occurred on this file */
        _ERR= 020     /* error occurred on this file */
    };
     
    基于函数库的控制台I/O:
    int putchar(char ch);
    int putc(int ch, FILE *stream);
    int getchar(); 
    int getc(FILE *stream);
    int puts(const char *p);
    char *gets(char *p);
    int printf(const char *format [, <参数表>]);
    int scanf(const char *format [, <参数表>]);
    putchar、getchar等其实是宏,定义如下:
    #define putchar(c) putc((c), stdout)
    #define putc(x,p) (--(p)->cnt>=0?*(p)->ptr++=(x):_flushbuf((x),p))      /*cnt是未写字符数*/
    #define getchar() getc(stdin)
    #define getc(p) (--(p)->cnt>=0?(unsigned char)*(p)->ptr++:_fillbuf(p))  /*cnt是未读字符数*/
    printf和scanf的需要给出格式字符串,其中包含普通字符与控制字符。
    控制字符对应的格式如下:
     
    输入时除了控制字符串规定的数据,还要输入格式字符串中所有普通字符,从而与输入字符实现匹配
     
     
    stdout和stderr都用于标准输出(显示器),但是,stderr存储模式为_WRITE | _UNBUF而stdout存储模式为 _WRITE
    因此stdout有缓冲:遇到换行符 或缓冲满(BUFSIZE=1024)才写文件!
    #include <stdio.h> 
    int main(){
        fprintf(stdout, “hello "); 
        fprintf(stderr, “world!");     
        return 0;
    };
    输出结果为:world!hello 
    #include <stdio.h> 
    int main(){
        fprintf(stdout, “hello ");     
        fprintf(stderr, “world!
    ");     
        return 0;
    };
    输出结果为:world!
                         hello
    #include <stdio.h>
    int main(){
        printf(stdout, “hello 
    "); 
        fprintf(stderr, “world!"); 
        return 0;
    };
    输出结果为:hello
                         world!
    二者都可重定位到普通文件中!
    #include <stdio.h> 
    void main(){
        fprintf(stdout, "from stdout
    "); 
        fprintf(stderr, "from stderr
    ”);
    };
    ./hello > out.txt:stdout送out.txt, stderr送屏幕
    ./hello 2 > err.txt:stdout送屏幕, stderr送err.txt 
    ./hello > out.txt 2> err.txt:stdout送out.txt,stderr送err.txt 
    ./hello > combine.txt 2>&1:stdout和stderr都送combine.txt 
    ./hello > combine.txt 2> combine.txt:stdout和stderr都送combine.txt
      
    基于函数库的文件I/O:
    FILE *fopen(const char *filename , const char *mode); //成功后会返回非空FILE *指针,失败则返回空指针
    mode包括:
    • w:如果外部文件已经存在,则先清除内容再打开,文件指针指向头部
    • a:文件指针指向尾部
    • r:打开一个存在的外部文件进行读操作
    后面还可以加上b(二进制方式打开)
    • w+:如果外部文件已经存在,则先清除内容再打开进行读/写操作,文件指针指向头部
    • a+:文件指针指向尾部
    • r+:打开一个存在的外部文件进行读/写操作
    后面还可以加上t(文本方式打开)或b(二进制方式打开)
    int fputc(char c , FILE *stream);
    int fgetc(FILE *stream);
    int fputs(const char *string , FILE *stream);
    char *fgetc(char *string , int n , FILE *stream);
    int fprintf(FILE *stream , const char *format [, <参数表>]);
    int fscanf(FILE *stream , const char *format [, <参数表>]);
    size_t fwrite(const void *buffer , size_t size , size_t count , FILE *stream);  //按字节输出数据
    size_t fread(const void *buffer , size_t size , size_t count , FILE *stream);
    #define feof(p) (((p) ->flag & _EOF) != 0)   //判断文件结束,文件位置指针在文件末尾时继续进行读操作会使feof返回true
    #define ferror(p) (((p) ->flag & _ERR) != 0)
    #define fileno(p) ((p) ->fd)
    文件位置指针可以显式获得和指定:
    long ftell(FILE *stream);
    int fseek(FILE *stream , long offset , int origin);  //返回0时移动成功
    offset为移动的字节数,可以为正(向后)或为负
    origin(参考位置)包括:
    • SEEK_CUR:当前位置
    • SEEK_END:文件末尾
    • SEEK_SET:文件头
    最后需要关闭文件:
    int fclose(FILE *stream);
     
    FILE中定义了1024字节的流缓冲区
    从文件fp中读数据时,FILE中定义的缓冲区为输入流缓冲(在内存)
    首先要从文件fp中读入1024(缓冲大小BUFSIZ=1024)个字节数据到缓存,然后,再按需从缓存中读取1个(如getc)或n个(如fread)字节并返回
    向文件fp中写数据时,FILE中定义的缓冲区为输出流缓冲
    先按需不断地向缓存写1个(如putc)或n个(如fwrite)字节,遇到换行符 或缓存被写满1024(缓冲大小BUFSIZ=1024)个字节,则将缓存内容一次写入文件fp中
     
    使用流缓冲区可使文件内容缓存在用户缓冲区中,而不是每次都直接读/写文件,从而减少执行系统调用次数。
    第一次调用getc(),需用_fillbuf()函数填充缓冲区:
    int _fillbuf(FILE *fp){     
        int bufsize;
        if ((fp->flag & ( _READ | _EOF | _ERR)) != _READ)
            return EOF;
        bufsize=(fp->flag & _UNBUF)?1:BUFSIZ;  //stderr没有缓冲即bufsize=1
        if ((fp -> base == NULL) /* 刚开始,还没有申请缓冲 */
            if (( fp -> base = (char *) malloc(bufsize)) == NULL) 
                return EOF; /* 缓冲没有申请到 */
        fp->ptr=fp->base;
        fp->cnt=read(fp->fd,fp->ptr,bufsize);    /* cnt<=1024,调用系统调用封装函数进行读文件操作,一次将输入缓冲读满*/    
        if (--fp->cnt < 0) {     /* cnt<=0 */
            if (fp->cnt == -1) fp->flag | = _EOF; 
            else fp->flag | = _ERR;
            fp -> cnt =0;
            return EOF;
        } /*一次将输入缓冲读满*/
        return (unsigned char)*fp->ptr++;   /* 0<cnt<=1023,返回缓冲区当前字节,并ptr加1*/
    }
    遇换行或写缓冲区满,调用其将缓冲内容写文件:
    int _flushbuf(int x, FILE *fp){   
        unsigned nc;
        int bufsize;
        if (fp < _iob || fp > _iob + OPEN_MAX)
            return EOF;
        if ((fp->flag & (_WRITE | _ERR)) != _WRITE)
            return EOF;
        bufsize=(fp->flag & _UNBUF) ? 1 : BUFSIZ;
        if (fp->base == NULL) {   /* 刚开始,还没有申请缓冲 */
            if ((fp->base = (char *)malloc(bufsize)) == NULL) {
                fp->flag |= _ERR; 
                return EOF;
            }
        } 
        else {    /* 已存在缓冲,且遇到换行符或缓冲已满 */
            nc = fp->ptr-fp->base;
            if (write(fp->fd,fp->base,nc)!=nc) {
                fp->flag |= _ERR; 
                return EOF
            }
        }
        fp->ptr=fp->base;   /* 缓冲区已空 */
        *fp->ptr++=x;
        fp->cnt=bufsize-1; 
        return x;
    }
     
    文件复制的两种方法比较总结:
    /* 方式一: getc、putc版本 */
    void filecopy(FILE *infp, FILE *outfp){
        int c;
        while ((c=getc(infp)) != EOF)
            putc(c, outfp);
    } 
    /* 方式二: read、write版本 */
    void filecopy(FILE *infp, FILE *outfp){
        char c;
        while (read(infp->fd,&c,1) != 0)
            write(outfp->fd,&c,1);
    }
    方式一通过_fillbuf()和_flushbuf()这两个函数一次性读写1024个字节。若文件长度为n,则执行系统调用的次数约为n/512。
    对于方式二,若文件长度为n,则需执行2n次系统调用。
  • 相关阅读:
    PAT (Advanced Level) 1060. Are They Equal (25)
    PAT (Advanced Level) 1059. Prime Factors (25)
    PAT (Advanced Level) 1058. A+B in Hogwarts (20)
    PAT (Advanced Level) 1057. Stack (30)
    PAT (Advanced Level) 1056. Mice and Rice (25)
    PAT (Advanced Level) 1055. The World's Richest (25)
    PAT (Advanced Level) 1054. The Dominant Color (20)
    PAT (Advanced Level) 1053. Path of Equal Weight (30)
    PAT (Advanced Level) 1052. Linked List Sorting (25)
    PAT (Advanced Level) 1051. Pop Sequence (25)
  • 原文地址:https://www.cnblogs.com/yangyuliufeng/p/9360180.html
Copyright © 2011-2022 走看看