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次系统调用。
  • 相关阅读:
    关于程序收到消息的顺序
    窗口过程
    消息循环
    解剖窗口程序
    开始了解窗口程序
    编码的规范
    汇编的除法和乘法
    Win32汇编--Win32汇编的高级语法
    H5-音频列表音乐切换
    移动端-ibokan
  • 原文地址:https://www.cnblogs.com/yangyuliufeng/p/9360180.html
Copyright © 2011-2022 走看看