zoukankan      html  css  js  c++  java
  • 《Linux应用文件编程(二) — 标准IO》

    1. 标准IO

      与文件IO函数相类似,标准IO库中提供的是fopen、fclose、fread、fwrite等面向流对象的IO函数,这些函数在实现时本身就要调用linux的文件IO这些系统调用。

    2. 标准IO的缓冲机制

       由于IO设备的访问速度与CPU的速度相差好几个数量级,为了协调IO设备与CPU的速度的不匹配,对于块设备,内核使用了页高速缓存,即数据会先被拷贝到操作系统内核的页缓存区中,然后才会从操作系统内核的缓存区拷贝到应用程序的地址空间。

      当应用程序尝试读取某块数据的时候,如果这块数据已经存放在页缓存中,那么这块数据就可以立即返回给应用程序,而不需要经过实际的物理读盘操作。当然,如果数据在应用程序读取之前并未被存放在页缓存中,那么就需要先将数据从磁盘读到页缓存中去。对于写操作来说,应用程序也会将数据先写到页缓存中去,数据是否被立即写到磁盘上去取决于应用程序所采用的写操作机制:如果用户采用的是同步写机制,那么数据会立即被写回到磁 盘上,应用程序会一直等到数据被写完为止;如果用户采用的是延迟写机制,那么应用程序就完全不需要等到数据全部被 写回到磁盘,数据只要被写到页缓存中去 就可以了。在延迟写机制的情况下,操作系统会定期地将放在页缓存中的数据刷到磁盘上。与异步写机制不同的是,延迟写机制在数据完全写到磁盘上得时候不会通 知应用程序,而异步写机制在数据完全写到磁盘上得时候是会返回给应用程序的。

      标准IO提供三种类型的缓冲:

    全缓冲:

      填满标准IO缓冲区后才进行实际的IO操作。对于在磁盘上的文件通常由标准IO库实施全缓冲的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓存区。

    行缓冲:

      当在输入和输出中遇到换行符时,标准IO库执行IO操作。通常涉及到终端(例如标准输入和标准输出)使用的是行缓冲。

      对于行缓冲有两个限制:

      第一,因为标准IO库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行IO操作。

      第二,任何时候只要通过标准IO库要求从(a)一个不带缓存的流,或者(b)一个行缓存的流(它要求从内核得到数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流(因为后面读取的数据可能就是前面输出的数据)。其实第二种情况我们会经常遇到,当我们先调用printf输出一串不带换行符的字符时,执行完这条printf语句并不会立刻在屏幕中显示我们输出的数据,当我们接下来调用scanf从标准输入读取数据时,我们才看到前面输出的数据。

    不带缓冲:

      标准IO库不对字符进行存储。例如,如果用标准IO函数fputs写15个字符到不带缓冲的流中,则该函数很可能直接调用write系统调用将这些字符立即写到相关的文件中。标准出错流stderr是不带缓冲的,这样为了让出错的信息可以尽快的显示出来。

    3. 标准IO函数

    3.1 fopen函数

    #include <stdio.h>
    
    FILE *fopen(const char *path, const char *mode);//mode为操作权限    //打开一个指定的文件
    FILE *fdopen(int fd, const char *mode);                           //取一个现存的文件描述符,并使一个标准I/O流与该描述符相结合
    FILE *freopen(const char *path, const char *mode, FILE *stream);  //用于重定向输入输出流的函数,将stream中的标准输入、输出、错误或者文件流重定向为filename文件中的内容。linux下需要重定向输出很容易使用 ./程序名 >test (>>test 追加),windows下的输入输出重定向可以使用freopen。
    返回值: 成功返回一个FILE文件流指针,失败返回NULL,并且设置errno全局变量。 参数:
       mode: r:以只读方式打开文件,该文件必须存在,返回的文件流指针位于文件开始 r+ :以读/写方式打开文件,该文件必须存在,返回的文件流指针位于文件开始 w:打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件 w+:打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件 a:以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留。 a+:以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留。
              r(read): 只读
              w(write): 只写
              a(append): 追加
              t(text): 文本文件,可省略不写。例如r=rt。以打开一个文本文件
              b(binary): 二进制文件
              +: 读和写

      stream: 文件指针,通常使用标准流文件(stdin/stdout/stderr)

         fdopen常用于由创建管道及网络通信通道函数返回的描述符(我们可能从 open,dup,dup2,fcntl,pipe,socket,socketpair或accept函数得到此文件描述符)。因为这些特殊类型的文件不能用fopen打开,因此必须先调用设备专用函数以获得一个文件描述符,然后再用fdopen使一个标准I/O流与该描述符相关联,形态必须和原先文件描述词读写模式相同。

      对于fdopen函数,mode的参数稍有区别:1.因为该描述符已被打开,所以fdopen为写而打开并不截短该文件。2.不能用于创建文件(因为一个描述符引用一个文件,则该文件一定存在)。

    例程:

    fopen和fdopen函数

    FILE *fp;
    int fd;
    if ((fp = fopen ("hello.txt", "w+")) == NULL)
        printf("fopen file error
    ");
        return 0;}
    fprintf(fp , "hello word
    ");
    fclose(fp);
    if ((fd = open("hello.txt", O_RDWR )) == 1) {
        printf("open file fail
    ");
        return 0;}
    if ((fp = fdopen( fd , "a+")) == NULL)
        printf("fdopen open
    ");
        return 0;
    }
    fprintf(fp , "linux c program");
    fclose(fp);

    freopen函数

    freopen("test.in", "r", stdin);
    程序的输入就会从标准输入流stdin转换到从文件"test.in"中输入。
    
    freopen("test.out", "w", stdout);
    程序的输出就会从原来的标准输出变成写入文件"test.out"中。
    

      

    3.2 fread函数

    #include <stdio.h>
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    
    返回值:
       成功时fread返回的值与nmemb相等;若小于nmemb但是大于0,则可能是到了文件末尾,不够次数;若返回0,则是文件读取错误,不满一个size的大小。
      所以循环读取文件的时候,while(可以判断是否大于等于0)作为条件,里面再基于feof函数判断是否到了文件末尾而退出循环。 参数: Ptr:读取的数据存储的位置 Size:读取的每个单元的大小 Nmemb:读取的单元数量 Stream:从哪个文件流读取数据

      fread不能区分到达文件尾和错误,因此必须使用feof和ferror函数确定是到达文件尾,还是发生错误。

     例程:

    int s[10];
     
    if (fread(s, sizeof(int), 10, fp) < 0) {
         perror(“fread”);
         return  -1;
    }

    3.3 fwrite函数

    #include <stdio.h>
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    
    返回值:
        成功时fwrite返回的值与nmemb相等;若小于nmemb表示出错,可以用perror函数查看错误原因。
    
    参数:
        prt:要写入的数据缓冲区
        size:每次写入的单元大小
        nmemb:写入的单元数
        stream:写入到哪个文件流
    

     例程1:

    struct  student {
           int  no;
           char name[8];
           float  score;
     
    } s[] = {{ 1, “zhang”, 97}, {2, “wang”, 95}};
     
    fwrite(s, sizeof(struct student), 2, fp);

     例程2:

    write.c

    #include <stdio.h>
    
    #define PATH  "./1.txt"
    
    typedef struct PEOPLE{
        char name[10];
        int age;
        char addr[10];
    }PEOPLE;  //name和addr不能用指针形式,不然用fwrite的时候,用sizeof计算会有问题,导致写入后乱码
    
    int main(void)
    {
        FILE *fp;
        PEOPLE Myself;
        int len;
    
        Myself.age = 24;
        strcpy(Myself.name, "zhuangsan");
        strcpy(Myself.addr, "shenzhen");
            
        fp = fopen(PATH, "w");
        if(fp == NULL)
        {
            perror("fopen:");
         return 0; } //sizeof计算的是已用内存长度,如果是字符串指针,用strlen len
    = fwrite(&Myself, sizeof(struct PEOPLE), 1, fp); if(len < 1) { perror("fwrite:");
         return 0; }
    return 1; }

    read.c

    #include <stdio.h>
    
    typedef struct PEOPLE{
        char name[10];
        int age;
        char addr[10];
    }PEOPLE;
    
    #define PATH   "./1.txt"
    
    
    int main(void)
    {
        PEOPLE Myself;
        FILE *fp;
        int len;
    
        fp = fopen(PATH, "r");
        if(fp == NULL)
        {
            perror("fopen:");
         return 0; } len
    = fread(&Myself, sizeof(struct PEOPLE), 1, fp); if(len < 1) { perror("fread:");
         return 0;  } printf(
    "name = %s ", Myself.name); printf("age = %d ", Myself.age); printf("addr = %s ", Myself.addr); return 1; }

    3.4 fclose函数

    #include <stdio.h>
    
    int fclose(FILE *fp);
    
    返回值:
        成功返回0,刷新缓存,失败返回EOF,并且设置errno。
    

      

    3.5 fseek函数

    #include <stdio.h>
    
    int fseek(FILE *stream, long offset, int whence);
    
    返回值:
        重定位成功返回0,否则返回非零值
    
    参数:
        stream:文件指针
        offset:指针的偏移量(该指针和文件指针不一样,该指针指的是文件内部数据)
        whence:指针偏移起始位置
            SEEK_SET  即0 文件开头
            SEEK_CUR 即1 文件当前位置
            SEEK_END 即2 文件结尾
    
    fseek(fp,100,SEEK_SET);把fp指针移动到离文件开头100字节处
    fseek(fp,100,SEEK_CUR);把fp指针移动到离文件当前位置100字节处
    fseek(fp,100,SEEK_END);把fp指针退回到离文件结尾100字节处

      fseek的作用:重定位文件内部的指针。

      需要注意的是该函数不是重定位文件指针,而是重定位文件内部的指针,让指向文件内部数据的指针移到文件中我们感兴趣的数据上,重定位主要是这个目的。

      说明:执行成功,则stream指向以whence为基准,偏移offset个字节的位置。执行失败(比方说offset偏移的位置超出了文件大小),则保留原来stream的位置不变。

    3.6 fflush函数 

    #include<stdio.h>
    
    int fflush(FILE *stream);
    
    参数:
        stream就是由fopen打开的文件流
    

      fflush函数把文件流里的所有未写出的数据立即写出。调用fclose函数隐含执行了一次fflush操作,所以不必在调用fclose之前调用fflush。

    3.7 fgetc、getc和getchar函数

    #include<stdio.h>
    
    int fgetc(FILE *stream);
    int getc(FILE *stream);
    int getchar();

      fgetc函数从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件尾或出现错误时返回EOF。

      getc函数作用同fgetc一样。

      getchar函数的作用相当于getc(stdin),从标准输入里读取下一个字符。

    fgetc和getc的区别:

      fgetc是一个函数,而getc是一个宏。一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。getc的参数不应当是具有副作用的表达式。宏都要避免用副作用表示式

      1. 很多RISC处理器(如SUN SPARC)使用寄存器窗口重叠技术,(http://server.chinabyte.com/404/157904_1.shtml)
      2.在寄存器充足的情况下,根本不需要堆栈操作,fgetc函数比getc宏更快
      3. 在多线程的情况下,为了线程同步,getc和fgetc可能是相同的。

    什么是副作用的表示式?

      

       在这个程序中,z2我想算8*8,但是传入的参数是5+3,6+2,宏替换之后就变成了5+3*6+2,最后结果就成了25。

    3.8 fputc、putc和putchar函数

    #include<stdio.h>
    
    int fputc(int c, FILE *stream);
    int putc(int c, FILE *stream);
    int putchar(int c);

      fputc函数把一个字符写到输出文件流中。返回值是写入的值,失败返回EOF。

      putc函数作用同fputc函数.

      putchar函数相当于putc(c,stdout),将字符c写到标准输出文件流中。

    注意:即使fgetc和fputc这些的返回值和参数都是int的,但是读取和写入都是以字符来传递的。

    3.9 fgets和gets函数

    #include <stdio.h>
    
    char *fgets(char *s, int n, FILE *stream);
    char *gets(char *s);
    参数:
    • str-- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
    • n-- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
    • stream-- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

      fgets函数把读到的字符写到s指向的字符串里,直到遇到换行符或者已经读取n-1个字符或者遇到文件尾。它会把遇到的换行符也传递到接收字符串里,再加上一个表示结尾的空字符尾0。

      fgets成功完成时返回一个指向字符串s的指针,如果文件流已经到达文件尾,fgets会设置这个文件流的EOF标识并返回一个空指针。如果出现读错误,fgets返回一个空指针并设置errno。

      与gets相比使用这个好处是:读取指定大小的数据,避免gets函数从stdin接收字符串而不检查它所复制的缓存的容积导致的缓存溢出问题。

      gets函数从标准输入读取数据并丢弃遇到的换行符,并在接收字符串尾部加上一个null字节。警告:gets对传输字符的个数并没有限制,所以它可能会溢出自己的传输缓冲区,所以应该避免使用gets函数。
    实例:

    #include<string.h>
    #include<stdio.h>
     
    int main ( void )
    {
        FILE*stream;
        char string[]="Thisisatest";
        char msg[20];
    
        stream=fopen("DUMMY.FIL","w+");
    
        fwrite(string,strlen(string),1,stream);
    
        fseek(stream,0,SEEK_SET);
    
        fgets(msg,strlen(string)+1,stream);
    
        printf("%s",msg);
        fclose(stream);
        return 0;
    }
    

     

    3.10 fputs和puts函数

    #include <stdio.h>
    
    int fputs(const char *str, FILE *stream);
    int puts(const char *str);
    
    参数:
        str:这是一个数组,包含了要写入的以空字符终止的字符序列
        stream:指向FILE对象的指针,该FILE对象标识了要被写入字符串的流
    返回值:
        函数返回值为非负整数,否则返回EOF(符号常量,其值为-1);
        成功写入一个字符串后,文件的位置指针会自动后移

      fputs是一个函数,具有的功能是向指定的文件写入一个字符串(不自动写入字符串结束标记符‘’)。

      puts()函数用来向标准输出设备(屏幕)输出字符串并换行,具体为:把字符串输出到标准输出设备,将''转换为回车换行。

    fputs和puts的区别:

      1.puts() 只能向标准输出流输出,而 fputs() 可以向任何流输出。

      2.使用 puts() 时,系统会在自动在其后添加换行符;而使用 fputs() 时,系统不会自动添加换行符。

      那么这是不是意味着使用 fputs() 时就要在后面添加一句“printf(" ");”换行呢?看情况!如果输入时使用的是 gets(),那么就要添加 printf 换行;但如果输入时用的是 fgets(),则不需要。

      因为使用 gets() 时,gets() 会将回车读取出来并丢弃,所以换行符不会像 scanf 那样被保留在缓冲区,也不会被 gets() 存储;而使用 fgets() 时,换行符会被 fgets() 读出来并存储在字符数组的最后,这样当这个字符数组被输出时换行符就会被输出并自动换行。

      但是也有例外,比如使用 fgets() 时指定了读取的长度,如只读取 5 个字符,事实上它只能存储 4 个字符,因为最后还要留一个空间给 '',而你却从键盘输入了多于 4 个字符,那么此时“敲”回车后换行符就不会被 fgets() 存储。数据都没有地方存放,哪有地方存放换行符呢!此时因为 fgets() 没有存储换行符,所以就不会换行了。

    实例:

    #include <stdio.h>
    #include <conio.h>
    int main(void)
    {
        int i;
        char string[20];
        for(i=0;i<10;i++)
            string[i]='a';
        string[10]='';
        puts(string);
        getch();
        return 0;
    }
    

      puts输出字符串时要遇到'’也就是字符结束符才停止,如上面的程序加上一句 string[10]='';

    3.11 printf、fprintf和sprintf函数

    #include <stdio.h>
    
    int printf(const char *format, ...);
    int sprintf(const *s, const char *format, ...);
    int fprintf(FILE *stream, const char *format, ...);
    

      printf函数将输出送到标准输出流。
      sprintf函数把输出加上一个结尾空字符送到字符串s中。
      fprintf函数把输出输送到指定的文件流中。

      format为输出格式类型,通常有以下几种常用的转换控制符:

      

    3.12 scanf、fscanf和sscanf函数

    #include<stdio.h>
    
    int scanf(const char *format, ...);
    int fscanf(FILE *stream, const char *format, ...);
    int sscanf(const char *s,const char *format,. ...);

      scanf系列函数使用一个格式字符串来控制输入数据的转化。
      scanf函数从标准输入流中读入数据。fscanf函数从指定的文件流中读入数据。sscanf函数则从指定的字符串中读入数据。

      format为输入格式类型,通常有以下几种常用的转换控制符:

      

       用法:https://blog.csdn.net/respectableking/article/details/76478897

    3.13 ftell函数、rewind函数

    #include <stdio.h>
    
    long ftell(FILE *stream);
    void rewind(FILE *stream);
    
    rewind()函数无返回值,fseek成功返回0,ftell成功返回当前偏移量,失败返回-1,并且设置errno。
    

      

    4. 例程

    例1:

    #include <string.h>
    #include <stdio.h>
    int main(void)
    {
       FILE *stream;
       char msg[] = "this is a test";
       char buf[20];
       if ((stream = fopen("DUMMY.FIL", "w+"))
           == NULL)
       {
          fprintf(stderr,"Cannot open output file.
    ");
          return 1;
       }
       /* write some data to the file */
       fwrite(msg, strlen(msg)+1, 1, stream);
       /* seek to the beginning of the file */
       fseek(stream, SEEK_SET, 0);
       /* read the data and display it */
       fread(buf, 1, strlen(msg)+1, stream);
       printf("%s
    ", buf);
       fclose(stream);
       return 0;
    }
    

    例2:

    #include  <stdio.h>
    #include  <stdlib.h>
    int main(int argc,char *argv[])
    {
        FILE *fp1, *fp2;       //流指针
        char buf[1024];        //缓冲区
        int n;      //存放fread和fwrite函数的返回值
        if(argc <=2)    //如果参数错误
        {
           printf("请输入正确的参数
    !");   //参数错误
        }
        if ((fp1 = fopen(*(argv+1), "rb")) == NULL)
        //以只读方式打开源文件,读开始位置为文件开头
        {
             printf("读源文件%s发生错误
    ",*(argv+1));
             return 1;      //出错退出
          }
        if ((fp2 = fopen(*(argv+2), "wb")) == NULL)
        //以只写方式打开目标文件,写开始位置为文件结尾
        {
             printf("打开目标文件%s失败
    ",*(argv+2));
             return 2;       //出错退出
          }
       //开始复制文件,文件可能很大,缓冲一次装不下,所以使用一个循环进行读写*/
        while ((n = fread(buf, sizeof(char), 1024, fp1)) > 0)
        {
            //读源文件,直到将文件内容全部读完*/
            if (fwrite(buf, sizeof(char), n, fp2) == -1)
            {
                //将读出的内容全部写到目标文件中去
                printf("写如文件发生错误
    ");
                return 3;    /*出错退出*/
            }
        }
        printf("从源文件%s读数据写入目标文件%s中完成
    ",*(argv+1),*(argv+2)); //输出对应的提示
        if(n == -1)
        {
             //如果因为读入字节小于0而跳出循环,则说明出错了*/
             printf("读文件发生错误
    ");
             return 4;     /*出错退出*/
        }
        fclose(fp1);   /*操作完毕,关闭源文件和目标文件*/
        fclose(fp2);
        return 0;
    }
    

      

    关于换行符的一些:https://blog.csdn.net/weixin_44879687/article/details/89739899

  • 相关阅读:
    《Go并发编程实战》读书笔记-初识Go语言
    使用Nexus配置Maven私有仓库
    Maven 本地资源库配置
    Django 2.2.x版本的ORM API实战案例
    在Mac OS环境下安装MySQL服务
    Pycharm搭建Django开发环境
    Hadoop生态圈-单点登录框架之CAS(Central Authentication Service)部署
    Ambari集成Kerberos报错汇总
    Hadoop生态圈-开启Ambari的Kerberos安全选项
    Hortonworks官网文档怎么找?
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/13064134.html
Copyright © 2011-2022 走看看