文件IO---------------标准IO

目录

前言:

1.标准IO是什么,为什么需要标准IO

    1.1    标准IO的文件有三种缓冲区类型:

1.2  setvbuf 函数(设置程序的FILE缓冲区函数)

2.常见的标准IO的函数接口

    2.1.打开或者关闭一个文件流(fopen/fclose)

打开

关闭

    2.2.读写一个文件流(fread/fwrite)

        a.每一次一个字符的读写

            fgetc/getc/getchar---------单字输入函数

            fputc/putc/putchar-----单字输出函数

        b.每一次一行的读写

            fgets/gets ------行输入函数

            fputs/puts---------行输出函数

        c.直接读写,操作的数据由用户自定义

    3.冲洗/同步一个文件流(fflush)

    4.定义一个文件流(fseek、ftell)

    5.文件出错/文件结束标记(feof)

    6.格式化的输入输出(scanf/printf)

        a.格式化输入(按照指定的格式输入数据)

        b.格式化输出(按照指定的格式输出数据)


前言:

        众所周知文件IO可以分为系统IO和标准IO,而系统IO指的是操作系统(glibc库)提供给用户操作文件的接口(open/close/read/write/lseek...),那么标准IO指的是什么呢?


1.标准IO是什么,为什么需要标准IO


    在每一个操作系统下面,对文件的管理和接口都是不一样的,  Linux的系统IO、windows:CreatFile....

在linux系统中流程多为:
        APP---->进程文件表项的下标(文件描述符)--->struct file *---->struct file ----->struct inode ----->硬件 

Linux系统IO操作函数:open/close/read/write/lseek...


同一个文件,在不同的操作系统下面,操作文件的方法和代码可能不一样

    但是C语言是几乎所有操作系统都支持的语言,使用C语言调用系统IO写的操作文件的代码地方移植性就非常差(使用了CreatFile,就不能在Linux下面运行,使用了open/read就不能运行到其他的系统中(没有glibC)),于是C语言标准委员会,就觉得它有责任,来统一文件操作的接口。
        ======>
        标准IO:C语言标准委员会统一的一套操作文件的接口
        标准IO只能操作普通文件(不能操作设备文件/目录文件)
            普通文件:
                普通的文本文件
                    没有特殊组织和格式的文件,以字符的ASCII码来顺序解析的文件
                    .txt  .c  .h   .cpp  .sh  ....
                    能够使用记事本打开的
                二进制文件
                    内部有特殊格式的文件,文件的内容需要按照特定的格式去解析
                    某一些字节可能会代表特殊的含义(不能使用记事本打开)
                    可执行文件, .bmp,.jpg, .doc .....

    在标准IO中,使用结构体(FILE)来描述一个打开的文件,结构体内部记录了文件的一些信息,结构体中也创建了两个缓冲区(两段可以使用的内存区域),一个读缓冲,一个写缓冲

文件IO---------------标准IO_第1张图片
   

同时还提供了对"普通文件"操作的函数接口
    fopen/fclose/fwrite/fread/fseek/gets/puts/printf/scanf
    只要支持C语言就可以使用标准IO的函数

    APP通过标准IO去操作文件的流程:
        APP--->标准IO的库函数(fopen/fclose)----->对应平台的系统IO--->内核-->硬件

    使用标准IO,每打开一个文件,就使用一个FILE类型的结构体表示,FILE内部有两个缓冲区:
        *out ---->  写缓冲区
        *in ----->  读缓冲区
        缓存区的作用主要是为了提高操作的效率
        标准IO文件是带缓冲区的IO,也叫做IO流(文件流),它的效率比系统IO高。

区别:
            系统IO:
                read  1字节,会把操作文件的所有流程走一遍,需要访问一次硬件 
                write  1字节,会把操作文件的所有流程走一遍,需要访问一次硬件 

            标准IO:
                fread: 1字节,会把操作文件的所有流程走一遍,访问一次硬件 
                        但是会直接从硬件上面读取一块内容,放到缓冲区
                        下一次还需要读的时候,就不需要访问硬件,直接从缓冲区读取
                fwrite: 1字节,不会立即同步到硬件,而是把内容写入到缓冲区
                        等待用户刷新或者缓冲区的内容满了的时候才会去访问一次硬件

文件IO---------------标准IO_第2张图片

    1.1    标准IO的文件有三种缓冲区类型:


            行缓冲:缓冲区的内容达到了一行(默认是1024个字节,可以设置),自动同步到硬件上面去,假设你设置行缓冲的大小最多是100个字节,缓冲区的数据达到了100个字节的时候,就会自动同步到硬件
                printf------>行缓冲  (stdout是行缓冲)
                遇到'\n'也会把数据同步到硬件上面去

            全缓冲:缓冲区的数据需要填满整个缓冲区,才会把数据同步到硬件上面去
                普通的文件就是全缓冲

            无缓冲:缓冲区中只要有数据就会同步到硬件上面去
                perror----->无缓冲   (stderr是无缓冲)

1.2  setvbuf 函数(设置程序的FILE缓冲区函数)

        也可以通过函数自己设置程序的FILE缓冲区类型和大小(setvbuf)
        NAME
            setbuf, setbuffer, setlinebuf, setvbuf - stream buffering operations  操作文件的缓冲区
        SYNOPSIS
            #include
            void setbuf(FILE *stream, char *buf);
            void setbuffer(FILE *stream, char *buf, size_t size);
            void setlinebuf(FILE *stream);
            int setvbuf(FILE *stream, char *buf, int mode, size_t size);

        在系统IO中操作系统会为每一个进程打开三个文件
            标准输入文件(键盘)  文件描述符  STDIN_FILENO    0
            标准输出文件(终端)  文件描述符  STDOUT_FILENO    1
            标准错误文件(终端)  文件描述符  STDERR_FILENO    2    
    
        在标准IO中,会为每一个进程打开三个标准IO文件流(FILE),对应系统IO打开的三个文件
            标准输入文件流  FILE *stdin 
                stdin是声明在中的一个全局变量,它指向标准输入设备(键盘)
                scanf默认就是从sdtin中获取数据

            标准输出文件流  FILE *stdout
                stdout是声明在中的一个全局变量,它指向标准输出设备(终端),有缓冲区
                printf就是把数据输出到stdout中

            标准出错文件流  FILE *stderr
                stderr是声明在中的一个全局变量,它指向标准输出设备(终端),没有缓冲区
                perror就是把数据输出到stderr中


2.常见的标准IO的函数接口


    2.1.打开或者关闭一个文件流(fopen/fclose)

打开


        NAME
            fopen, fdopen - stream open functions
        SYNOPSIS
            #include
            fopen是用来打开一个普通文件的
            FILE *fopen(const char *pathname, const char *mode);
                pathname:你要打开的文件的路径名(可以是相对路径/可以是绝对路径)
                mode:打开方式
                "r":只读打开,文件不存在,则报错
                    打开后,光标在文件开头
                    相当于 O_RDONLY   

                "r+":读写打开,文件不存在,则报错
                    打开后,光标在文件开头
                    相当于 O_RDWR  

                "w":只写打开,文件不存在,则创建
                    打开后,文件内容截短(文本文件的内容会被清空)
                    相当于 O_WRONLY | O_CREAT | O_TRUNC

                "w+":读写打开,文件不存在,则创建
                    打开后,文件内容截短(文本文件的内容会被清空)
                    相当于 O_RDWR | O_CREAT | O_TRUNC

                "a":追加打开(可写),文件不存在,则创建
                    打开后,文件的光标在末尾,文件的内容不会被截短
                    相当于 O_WRONLY | O_CREAT | O_APPEND

                "a+":追加打开(可读可写),文件不存在,则创建
                    打开后,文件原始读的光标在开头,文件原始写的光标在末尾
                    进行读操作,光标在前面,进行写操作,光标总在最后
                    
                    相当于 O_RDWR | O_CREAT | O_APPEND 

              ┌─────────────┬───────────────────────────────┐
              │fopen() mode │ open() flags                  │
              ├─────────────┼───────────────────────────────┤
              │     r       │ O_RDONLY                      │
              ├─────────────┼───────────────────────────────┤
              │     w       │ O_WRONLY | O_CREAT | O_TRUNC  │
              ├─────────────┼───────────────────────────────┤
              │     a       │ O_WRONLY | O_CREAT | O_APPEND │
              ├─────────────┼───────────────────────────────┤
              │     r+      │ O_RDWR                        │
              ├─────────────┼───────────────────────────────┤
              │     w+      │ O_RDWR | O_CREAT | O_TRUNC    │
              ├─────────────┼───────────────────────────────┤
              │     a+      │ O_RDWR | O_CREAT | O_APPEND   │
              └─────────────┴───────────────────────────────┘


            
            返回值:
            成功返回打开的文件的文件指针(FILE *)
            在标准IO中,FILE*表示一个打开的文件,后面的标准IO库函数操作文件都是使用这个指针

            失败返回NULL,同时errno被设置

关闭

        NAME
            fclose - close a stream
        SYNOPSIS
            #include
            int fclose(FILE *stream);
            关闭一个流,会先同步文件,并且关闭底层的文件描述符

    2.2.读写一个文件流(fread/fwrite)

        读写一个文件流可以使用多种函数,我们可以将这些函数分为三类。
        a.每一次一个字符的读写
            fgetc/getc/getchar
            fputc/putc/putchar
        b.每一次一行的读写
            fgets/gets 
            fputs/puts
        c.直接读写,操作的数据由用户自定义
            fread/fwrite 


        a.每一次一个字符的读写


            fgetc/getc/getchar---------单字输入函数


            NAME
                fgetc, getc, getchar- input of characters 
            SYNOPSIS
                #include
                fgetc和getc都是从指定的文件流中读取一个字符,返回读取到的字符的ASCII码
                int fgetc(FILE *stream);
                int getc(FILE *stream);

                返回值:
                    成功返回读取到的字符的ASCII码
                    失败返回-1,同时errno被设置
                

                int asc = fgetc(fp);
                or 
                int asc = fgetc(stdin);

                getchar是从标准输入(stdin)中读取一个字符,返回读取到的字符的ASCII码
                int getchar(void);

                getchar() <====> fgetc(stdin)

            fputc/putc/putchar-----单字输出函数


            NAME
                fputc, putc, putchar- output of characters 
            SYNOPSIS
                #include
                fputc和putc是用来把指定的字符c输出到stream指定的文件流中去
                int fputc(int c, FILE *stream);
                int putc(int c, FILE *stream);

                c:你要输出的字符的ASCII码
                返回值:
                    成功返回实际写入文件的字符的ASCII码
                    失败返回-1,同时errno被设置

                int ret = fputc('X',fp);
                int ret = fputc('X',stdout);
                fputc和putc的区别:fputc是一个函数,而putc可能是一个宏

                int putchar(int c);

        b.每一次一行的读写


            fgets/gets ------行输入函数


            NAME
            fgets - input of strings

            SYNOPSIS
                #include
                
                gets是用来从标准输入文件流中获取一行
                把读取到的数据保存到s指向的空间中去
                char *gets(char *s);  (有bug,不要使用)
                    s:是一个指针,指向一块可用的空间,用来保存读取到的字符串的
                返回值:
                    成功返回s的首地址
                    失败返回NULL,同时errno被设置

                NOTES:没有考虑到s指向的空间的大小问题,有可能会造成内存的非法访问
                ========>
                fgets的作用是从stream指定的文件流中获取至多size个字符
                把获取到的数据保存到s指向的空间中去

                char *fgets(char *s, int size, FILE *stream);
                    s:是一个指针,指向一块可用的空间,用来保存读取到的字符串的
                    size:表示你最多获取size个字符(一般是s指向的空间的可用长度)
                        输入结束有两种情况
                        a.遇到'\n'或者文件结束了(\n也会被读取)
                        b.已经读取了size-1个字符(最后一个字符存储'\0')
                    stream: 
                        表示你要从哪一个文件中读取数据
                    
                返回值:
                    成功返回s的首地址
                        为什么很多函数都返回自己传进去的地址(gets/fgets/strcpy/strcat...)
                        目的是为了让函数可以写成链式表达式(让函数表达式可以作为其他函数的参数)

                        失败返回NULL,同时errno被设置

                        char buf[1024] = {0};
                        char *s = fgets(buf,1024,stdin);
                        int r = strlen(buf);  
                        ==========>
                        int r = strlen( fgets(buf,1024,stdin) );  

                   


            fputs/puts---------行输出函数


                NAME
                    fputs, puts - output of  strings
                SYNOPSIS
                    #include
                    fputs是用来把s指向的字符串,输出到stream指定的文件流中去
                    int fputs(const char *s, FILE *stream);
                        s:指针,表示你要输出的字符串的首地址(从s的位置开始一个字符一个字符的输出,直到遇到\0为止)
                    返回值:
                        成功返回一个非负数
                        失败返回-1,同时errno被设置

                    puts是用来把s指向的字符串,输出到stdout
                    但是会把字符串最后的'\0'转换为一个'\n'
                    int puts(const char *s);

        c.直接读写,操作的数据由用户自定义


            fread/fwrite 这两个函数都是直接操作缓冲区

            NAME
                fread, fwrite - binary stream input/output
            SYNOPSIS
                #include
                fread是用来从stream指定的文件流中读取nmemb个对象,并且每一个对象有size个字节,读取到的内容保存到ptr指向的内存空间中去
                总共读取了:  size * nmemb
                size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
                    ptr:是一个指针,指向一块可用的空间,用来保存读取到的数据
                    size:每一个数据对象的大小
                    nmemb:你要读取多少个数据对象
                    stream:表示你要从哪一个文件中读取数据 
                    返回值:
                        成功返回实际读取到的元素个数(<= nmemb)
                        失败返回-1,同时errno被设置

                fwrite是用来把ptr指向的nmemb个元素写入到stream指定的文件中去,每一个元素的大小是size
                总共写入了:  size * nmemb
                size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
                    ptr:是一个指针,指向你要写入的数据
                    size:每一个数据对象的大小
                    nmemb:你要写入多少个数据对象
                    stream:表示你要把数据写入到哪一个文件中
                    返回值:
                        成功返回实际写入的元素个数(<= nmemb)
                        失败返回-1,同时errno被设置


    3.冲洗/同步一个文件流(fflush)


        标准IO是带缓冲区的IO,读和写的操作都是针对于缓冲区的,并不是每一次读取都会同步到硬件,有时候多线程编程就会产生不可预测的结果
        可以手动把缓冲区中的内容同步到硬件
        NAME
            fflush - flush a stream
        SYNOPSIS
            #include
            int fflush(FILE *stream); 
                stream:你要同步的文件流
            返回值:
                成功返回0
                失败返回-1.同时errno被设置

        注意:
            对于普通文件
                输出流(out):fflush会把缓冲区的内容直接更新到文件中去
                输入流(in):fflush把缓冲区的内容直接丢弃(下一次读取,会直接从文件中读取)
            stream为NULL,会把当前进程打开的所有的文件都进行一个同步
            fflush(NULL)            


    4.定义一个文件流(fseek、ftell)


        上面讲到fread/fwrite,只是从指定的文件中读取/写入数据,并没有指定从文件的哪一个位置开始读写
        标准IO同样会为每一个文件保存一个"文件偏移量",offset(光标)下一次读和写的起始位置,并且每一次的读写,都会改变光标位置
        一般来说,读和写之前,都需要重新定位光标的位置

        NAME
            fgetpos, fseek, fsetpos, ftell, rewind - reposition a stream

        SYNOPSIS
            #include

            int fseek(FILE *stream, long offset, int whence);
                stream:你要定位的文件流
                offset:偏移量,可正可负,配合第三个参数使用
                whence:定位方式,注意有三种
                    SEEK_SET        基于文件开头定位
                        新位置 = 文件开头 + offset(>=0)
                    SEEK_CUR        基于文件当前位置定位
                        新位置 = 文件当前光标位置 + offset(可正可负)
                    SEEK_END        基于文件结尾定位
                        新位置 = 文件结尾 + offset(可正可负)

                返回值:
                    成功返回0
                    失败返回-1,同时errno被设置

            ftell返回当前文件流光标离文件开头有多少个字节
            long ftell(FILE *stream);

            fseek(fp,0,SEEK_END); //把光标定位到文件末尾
            long size = ftell(fp);

            //把文件光标定位到文件开头
            void rewind(FILE *stream);
            等价于:
                fseek(stream, 0, SEEK_SET);

    5.文件出错/文件结束标记(feof)


        EOF(end of file):宏,文件结尾标记
        NAME
        feof - check and reset stream status

        SYNOPSIS
            #include
            feof是用来判断一个文件是否到达末尾(结束)
            int feof(FILE *stream);
            返回值:
                文件流结束(光标到达了最后),返回真(非0)
                文件还没有到达末尾,返回假

        while( !feof(fp) ) //文件没有结束,就可以进入循环
        {
            ...
        }

        在读到文件末尾的时候,再一次读的时候就会自动的往缓冲区中填入一个字节
        EOF:二进制  1111 1111    ------> -1


    6.格式化的输入输出(scanf/printf)


        a.格式化输入(按照指定的格式输入数据)


            scanf/sscanf/fscanf
            NAME
            scanf, fscanf, sscanf - input format conversion

        SYNOPSIS
            #include
            ...:在C语言里面表示可变参数
            int scanf(const char *format, ...);
                scanf可以带很多个参数,参数分为两类
                scanf("%d%d%d",&a,&b,&c);
                第一类是第一个参数:格式化字符串(format string)
                    规定用户的输入方式,你必须要按照格式化字符串指定的格式一模一样的输入,不然就会失败
                    如:
                    scanf("abcd%d",&x);
                    "abcd%d"就是格式化字符串,在输入的时候一定要输入abcd+整数(不然就会匹配失败),abcd123
                    格式化字符串由三类字符组成:
                    a.空白字符(space tab)
                        指示用户,输入的时候,此处需要分隔符,用户可以输入任意数量的空白符(包括0个)
                    b.非转义字符
                        在输入的时候,普通字符需要精准匹配,你必须按照格式化字符串一模一样的输入 
                    c.转义字符(以%开头)
                        %d----->匹配整数
                        %c----->匹配一个任意的字符
                        %f----->匹配一个浮点数
                        %s----->匹配一个字符串(中间不能有空白,scanf会把空白当前分隔符)
                        ...

                其他的参数是第二类参数(地址列表):
                    格式化字符串中每一个转义字符都会对应一个地址,把一个转义字符匹配到的输入存储到指定的地址上去,如果转义字符的个数大于地址个数,程序的行为是未定义的
                
                scanf从标准输入中获取数据,如何结束?
                    a.该输入的都输入完了
                        格式化字符串中所有规定的内容都匹配成功
                        scanf("abcd%d %dabcd",&a,&b);
                        输入:abcd123 456abcd 
                            所有的字符都匹配成功
                    b.scanf匹配失败了
                        scanf("abcd%d %dabcd",&a,&b);
                        输入:abab123 456abcd 
                        匹配失败,停止匹配,直接结束函数
                        匹配失败之后,内容依然在标准输入的缓冲区
                返回值:
                    返回匹配成功的变量的个数

            ========================================================
            fscanf它的功能以及返回值与scanf类似,只不过fscanf的输入来源不是标准输入(stdin),而是从通过stream指定的文件流中读取
            int fscanf(FILE *stream, const char *format, ...);

            sscanf它的功能以及返回值与scanf类似,只不过sscanf的输入来源不是文件,而是从通过str指定的内存中读取
            int sscanf(const char *str, const char *format, ...);
            如:
                char *s = "1234";
                int a;
                sscanf(s,"%d",&a);

        b.格式化输出(按照指定的格式输出数据)


            printf/fprintf/sprintf/snprintf
            NAME
            printf,  fprintf,  sprintf, snprintf - formatted output conversion

        SYNOPSIS
            #include
            printf是按照格式化字符串输出内容到标准输出文件(stdout)
            int printf(const char *format, ...);
                printf可以带很多个参数,参数分为两类
                printf("a = %d,b = %d,c = %d",a,b,c);
                第一类是第一个参数:格式化字符串(format string)
                    规定用户的输出方式,会按照格式化字符串指定的格式一模一样的输出
                    如:
                    printf("a = %d,b = %d,c = %d",a,b,c);
                    "a = %d,b = %d,c = %d"就是格式化字符串,在输出的时候会一模一样输出到stdout
                    格式化字符串由2类字符组成:
                    a.非转义字符
                        在输出的时候,普通字符会一模一样输出到stdout,不会有任何改变
                    b.转义字符(以%开头)
                        %d----->把后面指定的数据对象,以10进制整型输出
                        %x----->把后面指定的数据对象,以16进制整型输出
                        %c----->把后面指定的数据对象,以字符形式输出
                        %f----->把后面指定的数据对象,以浮点数输出
                        %u----->把后面指定的数据对象,以无符号10进制输出
                        %s----->字符串(从一个地址开始,一个字符一个字符的输出,直到遇到\0)
                        ...

                其他的参数是第二类参数(变量列表):
                    格式化字符串中每一个转义字符都会对应一个"输出对象",要输出的对象的数量应该与转义字符的个数一样
                
                返回值:
                    返回实际输出到终端的字符数量(包括转义字符转换之后的数据)


            fprintf的功能返回值与printf类似,只不过fprintf是把内容输出到stream指向的文件流中,而不是stdout
            int fprintf(FILE *stream, const char *format, ...);

            sprintf的功能返回值与printf类似,只不过sprintf是把内容输出到str指向的内存中,而不是stdout
            int sprintf(char *str, const char *format, ...);
                返回值:
                    返回实际输出到内存的字符数量
                只不过,sprintf有一个bug,不应该被使用

                str只是一个内存的起始编号,并没有指定str的可用长度,有可能会造成内存越界
            ======>
                snprintf的功能返回值与sprintf类似,只不过snprintf中多了一个参数size,size的作用是指定str表示的空间的大小
            int snprintf(char *str, size_t size, const char *format, ...);
                返回值:
                    返回理论应该输出的字符串长度,而不是实际输出的长度
                    实际输出的长度就是str中字符串的长度
 

            例子:
                char buf[10] = {0};
                char *str = "123456789abcdefg";
                int r = snprintf(buf,10,"%s",str);
                r---->16  理论应该输出的字符数量是16
                if(r>10)
                {
                    输出不完整!!
                }


 

你可能感兴趣的:(单片机,stm32,嵌入式硬件)