文件IO(二.标准IO)

标准IO


1.为什么需要标准IO?


    每个操作系统下面,对文件的管理和接口是不一样
        linux: open,read,write......
        window:winopen.....
    同一个文件,在不同的操作系统下面,操作文件的代码是不一样。
    此时,代码就比较难写,我们在linux下去操作文件要写一段代码,但是这段代码到window下用不了,这个时候又得写另外一段
    代码。所以大部分的程序猿对于文件系统下面文件操作标准需要统一的呼声比较高。那么此时我们的C标准委员会就开发了一套
    比较适用于大部分操作系统的文件接口,这一套接口就叫做"标准IO"

    
2.文件的种类


    文件分成普通的文本文件和二进制文件。
        文本文件:无组织,无格式的文件,以字符的ASCII码来解析的文件
            如 : .txt  .c  .h  .cpp .java....
        二进制文件:有特定的格式的文件
            如: .jpg  .bmp  .gif  .mp3  .mp4 
    【文本类的文件使用ASCII码或者UTF-8来解析二进制数据的】
    【ASCII码是UTF-8编码模式的子集】


3.标准IO的FILE结构体和文件缓冲区


    在标准IO库中,用结构体FILE来表示一个文件,FILE有两个成员变量,一个是写缓冲区,一个是读缓冲区(缓冲区就是一块内存)
        FILE
        {
            char *in //指向的是读缓冲区
            char *out //指向的是写缓冲区
            ......
            .....
        };
    【标准IO的FILE结构体就像系统IO中的file结构体】
    【在系统IO中,是通过fd找到被打开文件的file结构体】
    【在file结构体里面找到inode和文件内容】
    【文件的inode和内容都是存放在硬盘中的】
    【file结构体和FILE结构体的数据节点都是存放在内存中的】
    【file和FILE结构体存在的意义就是减少频繁访问硬盘的开销】
    【除了inode和文件内容,其他的file结构体基本上都是在保存在内存当中的】


    那么我们标准IO缓冲区是分成三种类型的:
        a. 行缓冲
            缓冲区数据达到一行,同步到硬件上。
            假设一行为80个字节,那么缓冲区的数据如果达到80个字节或遇到\n,就会把缓冲区的数据同步到硬件上。
            比如我们经常使用的printf就是一个行缓冲的函数
            int main()
            {
                printf("1");
                while(1);
            }
        b. 全缓冲 
            缓冲区中数据填满,才进行数据的同步
        
        c. 无缓冲
            来一个字节数据,就输出一个字节。perror()就是一个无缓冲的函数。
            int main()
            {
                printf("123");
                perror("abc");
                while(1);
            }
        
    【文件的读写缓冲区通常存储在计算机的内存中】
    【内存是一个用于暂时存储正在被处理的数据的区域,它比较快速和易于访问】
    【当文件被读取或写入时,数据首先被加载到缓冲区,然后通过输入输出(I/O)操作传输到磁盘或其他存储介质】
    【这样可以提高文件的读写效率,减少频繁访问磁盘的开销】
    【缓冲区的作用就是减少频繁的访问硬盘,因为你访问硬盘的速度比访问内存要慢的多】
    【缓冲区是针对标准IO存在的】


4.标准IO不仅提供缓存区,还提供文件操作的函数


    我们的标准IO不仅提供缓冲区,还提供了对文件操作的接口函数:
        fopen/fclose/fread/fwrite/fseek
        puts/gets/fputs/fgets/scanf/printf.....
        那么这些标准IO的函数的操作文件的流程是什么?
        在我们的代码中调用了标准IO函数(fopen)-->标准IO函数调用了系统IO函数-->内核-->硬件
    这样做有什么好处?
        我们之前的代码的步骤是直接在代码中调用了系统IO,现在的代码流程就相当于多出了一层,就是多了调用标准IO这一步。
    流程好像是更复杂了,可移植性更强了,因为标准IO会根据所处于的操作系统的环境自动去选择适应的系统IO函数,比如:代码
    运行在linux下,调用Linux下的系统IO,如果代码运行在window下,调用window下的系统IO。
    【标准IO的文件操作,也是通过调用系统IO的方式来操作文件】
    那么既然标准IO最终还是通过调用系统IO的方式去实现操作文件的目的,哪种方式效率更高?
    标准IO是一种带缓冲流的IO流,它的效率是比系统IO高
    比如如果要连续不间断的写一个字节到一个文件:
        那么系统IO的做法是每次写一个字节,连续不断往文件中写。
        标准IO就是把字节先累积到缓冲区,直到缓冲区满足它的条件或者进程结束,就会将缓冲区的内容一次性写到文件
    【标准IO拥有缓冲区,所以标准IO的效率要比系统IO高】
    比如:如果要从文件中读取内容(每次读取一个字节)
        系统IO做法就是从文件中一个字节一个字节的读取
        标准IO直接从文件中读取一块(512个字节)到缓冲区,如果下次还要读取的文件没有超过缓冲区里面的内容,就可以直接
        从缓冲区读取就不会访问文件。

    标准IO库,会自动为每个进程打开三个标准IO流(文件)
        标准输入流: FILE *stdin 
            stdin是定义在stdio.h中的一个全局变量,它指向标准输入设备(一般就是终端或键盘)
            scanf <-- stdin  
        标准输出流: FILE *stdout 
            stdout是定义在stdio.h中的一个全局变量,它指向标准输出设备(一般就是终端或屏幕)
            printf <-- stdout 
            
        标准出错流: FILE *stderr 
            stderr是定义在stdio.h中的一个全局变量,它指向标准出错设备(一般就是终端)
            perror() <-- stderr 

        【print和scanf都是标准IO提供给我们的缓冲流函数】

        【perror也是标准IO提供给我们的,但是他没有缓冲区】

        【标准输入,输出,出错文件并没有具体的依赖于某个硬件存在,它是linux抽象出来的文          件,处于系统内核中】

        【我们平时用的print,scanf也都是文件操作的函数,不过他们操作的默认文件是标准输入输          出文件】

       


            
 5.标准IO的函数接口


    (1)打开一个文件流


        fopen 
    NAME
       fopen, fdopen, freopen - stream open functions
        打开一个文件流
    SYNOPSIS
       #include <stdio.h>

    FILE *fopen(const char *pathname, const char *mode);
        @pathname:要打开的那个文件的路径文件名
        @mode:打开文件的方式,有如下的几种:
            "r":以只读的方式打开,文件不存在,则报错
                打开后,光标在开头
            "r+":读写打开。文件不存在,则报错
                打开后,光标在开头
            "w":只写打开,文件不存在,就会创建
                打开后,文件内容就会截短(文件内容被清空)
            "w+":读写打开,文件不存在,就会创建
                打开后,文件内容就会截短(文件内容被清空)
            "a":追加打开,文件不存在,则创建
                打开后,光标在末尾。
            "a+":读写打开,文件不存在,则创建
                ###原始读的位置在开头,原始写的位置在末尾。
    返回值: 
        成功的话返回一个已经打开的文件指针(FILE *)
        在标准IO中,FILE *代表一个打开的文件,后续的操作中用的到
        失败返回NULL,同时errno被设置
    


    (2)关闭一个文件流


        fclose 
    NAME
       fclose - close a stream
        关闭一个打开的文件流
    SYNOPSIS
       #include <stdio.h>

       int fclose(FILE *stream);
            @stream:要关闭的那个文件流
        返回值:
            成功的话,返回0,失败返回-1,同时errno被设置
    
    例子:
        "r"  只读    "r+" 读写 -->如果文件不存在会报错
        "w"  只写    "w+" 读写 -->如果文件不存在就会创建并将文件截短
        "a"  追加(只写)  "a+" 读写 --> 如果文件不存在就会创建并不会截短-->原始读的位置在开头,原始写的位置在末尾
            
    int main(int argc,char *argv[])
    {
        //将上面六种打开方式全部尝试一次 
        FILE *fp = fopen(argv[1],"a+");
        if(fp == NULL)
        {
            perror("fopen failed");
            return -1;
        }
        
        fclose(fp);
        return 0;
    }

    (3)读写流


        一旦读写成功的话,光标会自动往后移动n个位置(n就是你读写成功的字节数)
        a.每次一个字符读写 
            fgetc/getc/getchar
            fputc/putc/putchar
            
        头文件: 
            #include <stdio.h>
            
        1)fgetc 
            NAME
       fgetc - input of characters and strings
        fgetc用来从stream指定的文件流中读取下一个字符,并把字符的ASCLL码返回    
    SYNOPSIS
       #include <stdio.h>

       int fgetc(FILE *stream);
            @stream:指定的那个文件流
        返回值: 
            成功的话返回获取到的字符的ASCLL
            失败返回-1,并且errno被设置
        
        2)getc 
            getc和fgetc一样,也是用来从stream指定的文件中,读取下一个字符。getc和fgetc的区别在哪里?
            fgetc是一个函数,getc可以是用宏实现的
            
        3)getchar 
            int getchar(void);
            getchar是用来从标准输入流stdin中获取下一个字符,并把字符的ASCLL码返回
                比如: getchar() <==> fgetc(stdin)
            
    
        4)fputc

 
        NAME
       fputc - output of characters

    SYNOPSIS
       #include <stdio.h>

       int fputc(int c, FILE *stream);
            @c:要输出的那个字符的ascll码
            @stream:文件流,表示要输出到哪个文件中去
        返回值: 
            成功的话返回实际上写入到文件中的字符的ASCLL码 
            失败返回-1,并且errno被设置
        
        5)putc 
            putc与fputc功能与返回值一样,只不过putc的实现可能是用宏实现的
            
        6)putchar 
            int putchar(int c);
            putchar用来把c指定的字符输出到标准输出流stdout中
            比如: putchar(c)<==>fputc(c,stdout)
        练习: 
            利用fgetc和fputc实现,把从键盘上获取的字符,再回显到终端上,直到遇到'#',停止
            int main()
            {
                char c;
                while(1)
                {
                    c=fgetc(stdin);
                    if(c == '#')
                    {
                        break;
                    }
                    fputc(c,stdout);
                }
            }
    
        b.每次一行字符的读写
            fgets/gets 
            fputs/puts 
            
            1)gets 
            NAME
           gets - get a string from standard input (DEPRECATED)
                从标准输入流中获取一行字符,直到遇到\n,存储到S指向的内存空间中去。gets会将获取的换行符或者EOF
            自动置换为'\0'
                
            SYNOPSIS
               #include <stdio.h>

               char *gets(char *s);
                    @s:指向你获取了的字符串的首地址
            返回值:
                成功的话返回S指向的首地址
                失败的话返回NULL,同时errno被设置
            需要注意的是:
            gets有一个巨大的BUG,gets没有考虑到S指向的空间是否够用,存在一个越界的可能。
            例子: 
            int main()
            {
                char s1[5];
                char s2[7]="123456";
                printf("s1:%p\n",s1);
                printf("s2:%p\n",s2);
                gets(s1);
                printf("s1:%s\n",s1);
                printf("s2:%s\n",s2);
            }
            
            2)fgets 
                fgets修正了gets函数的bug。
                char *fgets(char *s, int size, FILE *stream);
                    @s:指向的空间是用来保存从文件流中读取的数据
                    @size:表示你最多获取size个字节,size一般为s指定的空间中可用的长度
                    fgtes输入结束有两种情况:
                        1.遇到\n符结束或者文件结束
                        2.已经读取到了size-1个字节(后面留一个\0的位置)
                    @stream:表示从哪个文件中读取
            返回值: 
                成功的话返回S
                失败的话返回NULL,同时errno被设置
                gets(s)<==>fgets(s,size,stdin);//size为无穷大
                ###### 
                gets会将输入字符串的最后'\n'接收并且置换为了'\0'(等效于gets没有接收\n),fgets也会接收'\n',
                但是不会将'\n'进行置换为'\0',它会在'\n'后面再加一个'\0'
            
            3)fputs 
                int fputs(const char *s, FILE *stream);
                    fputs用来把S指向的字符串输出到stream指定的文件流中
                    @s:指向要输出的字符串
                    @stream:要输出的文件
                返回值: 
                    成功的话返回一个非负数
                    失败的话返回-1,并且errno被设置
            4)puts 
                int puts(const char *s);
                puts用来把S指向的字符串输出到标准输出流stdout中去,会多输出一个'\n'
                返回值: 
                    成功的话返回一个非负数
                    失败的话返回-1,并且errno被设置
        c. 直接读写 
            fread/fwrite 
            1)fread  
            NAME
            fread, fwrite - binary stream input/output

            SYNOPSIS
                   #include <stdio.h>

            size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
                fread用来从stream指定的文件流中,读取nmemb个对象,每个对象size个字节(所以实际上是读取了nmemb*size个字节)
            读取的内容保存在ptr指向的内存空间中去
                @ptr:用来保存从文件流中读取的"数组"
                @size:每个元素所占的大小
                @nmemb:要读取多少个元素
                @stream:表示从哪个文件流中读取数据
            返回值: 
                ######成功的话返回实际上读取到的元素个数(并不是字节数)
                失败返回0,同时errno被设置
        
            2)fwrite
            size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
                fwrite用来把ptr指向的nmemb个元素(每个元素占size个字节)写入到stream指定的文件流中去
                @ptr:要写入的数组的首地址
                @size:每个元素所占的大小
                @nmemb:要读取多少个元素
                @stream:表示要写入到哪个文件流
            返回值: 
                ######成功的话返回实际上写入的元素个数(并不是字节数)
                失败返回0,同时errno被设置
        例子: 
            将10个int类型元素的数组写入到文件中去,再将它读取出来
            int main(int argc,char *argv[])
            {
                //打开文件 
                FILE *fp=fopen(argv[1],"w+");
                if(fp == NULL)
                {
                    perror("fopen failed");
                    return -1;
                }
                int a[10]={1,2,3,4,5,6,7,8,9,10};
                int ret = fwrite(a,4,10,fp);
                if(ret == 0)
                {
                    perror("fwrite failed");
                    return -1;
                }
                fclose(fp);
                //重新打开文件
                fp =fopen(argv[1],"r");
                if(fp == NULL)
                {
                    perror("fopen failed");
                    return -1;
                }
                int b[10]={0};
                ret = fread(b,4,10,fp);
                if(ret == 0)
                {
                    perror("fread failed");
                    return -1;
                }
                printf("成功读取到了%d个元素\n",ret);
                int i;
                for(i=0;i<10;i++)
                {
                    printf("%d ",b[i]);
                }
                putchar('\n');
                fclose(fp);
                
                return 0;
            }


    (4)冲洗流fflush同步


            NAME
       fflush - flush a stream
        
        SYNOPSIS
               #include <stdio.h>

        int fflush(FILE *stream);
            @stream:要同步的文件流
        返回值: 
            成功返回0,失败返回-1,并且errno被设置
            
            对输出流,fflush就会把写缓冲区的内容写/更新到文件中去;
            对输入流, fflush把读缓冲区的内容直接丢弃,这样的话你下次读,就可以重新从文件中读取内容
            stream 为NULL,fflush把该进程所有打开的文件的输出流全部冲洗一遍
        例子: 
            int main()
            {
                printf("hello");
                sleep(5);
                fflush(NULL);
                while(1);
                return 0;
            }
    

    (5)定位流


            "文件偏移量":offset,"光标"。直接确定下一次读/写的起始位置。
            fseek 
            NAME
       fgetpos, fseek, fsetpos, ftell, rewind - reposition a stream
            
        SYNOPSIS
           #include <stdio.h>

           int fseek(FILE *stream, long offset, int whence);
                @stream:表示你想要定位的哪个文件
                @offset:偏移量,可正可负 
                @whence:定位的方式,有如下的三种:
                    SEEK_SET  : 基于文件的开头位置 
                    SEEK_END  : 基于文件的末尾位置
                    SEEK_CUR  : 基于文件的当前位置 
                
            返回值: 
                成功返回0,失败返回-1,同时errno被设置
        -------------------------------------------------------    
            long ftell(FILE *stream);    
                ftell返回当前的光标的位置距离文件开头有多少字节
                
        -------------------------------------------------------
            void rewind(FILE *stream);
                rewind把文件光标,定位文件开头
                rewind(stream)<==>lseek(fd,0,SEEK_SET)<==>fseek(stream,0,SEEK_SET)
        

    (6)文件出错/结束标记 


            EOF:END of FILE  文件结束标记,从键盘上我们可以通过输入ctrl+D输入文件结束(EOF)符
            例子: 
            int main()
            {
                char c;
                while(1)
                {
                    c=fgetc(stdin);
                    if(c == '#')
                    {
                        break;
                    }
                    printf("c==%d\n",c);
                    fputc(c,stdout);
                    printf("---------------\n");
                }
            }
            从键盘上输入crtl+D,此时我们输入了一个EOF符,现象:打印了一个-1。其实EOF的值其实就是 1111 1111
            如果把上述的退出条件进行更改
            if(c == '#'|| c==EOF)
            {
                break; 
            }
            另外一个例子: 
            int main(int argc,char *argv[])
            {
                FILE *fp=fopen(argv[1],"r");
                if(fp == NULL)
                {
                    perror("fopen failed");
                    return -1;
                }
                char ch;
                while(1)
                {
                    ch=fgetc(fp);
                    
                    if(ch == -1)
                    {
                        perror("fgetc failed");
                        return -1;
                    }
                    printf("%c----%d\n",ch,ch);
                }
            }
        我们在读取完最后一行的\n之后,此时文件里只剩一个EOF,那么fgetc也能获取到EOF符,也就是-1.此时并没有出错
    但是我们进去if语句。怎么解决?
        为了解决这个问题,标准IO库中加入了一个函数:
        feof
        int feof(FILE *stream);
            feof用来判断stream指定的文件流是否结束。
            返回值: 
                如果文件到达末尾,返回真(非0)
                如果文件没有达到末尾,返回假(0)
                
        以上的问题的解决:
        只要在ch=fgetc(fp);后面加上
            if(feof(fp))
            {
                break;
            }


    (7)格式化IO


        a.格式化输入 


            scanf/sscanf/fscanf
            "格式化输入":按照系统设定的格式进行输入
            1) int scanf(const char *format, ...);
                scanf可以带很多个参数,scanf的参数可以分成两类:
                第一个参数为第一类,格式化的字符串:
                在格式化的字符串中分成三类字符:
                1. 空白符
                    指示用户可以输入任意数量的空白符。
                    换行符不包含在内,因为scanf把\n当做是输入结束。
                    例子: 
                        int main()
                        {
                            int  a,b;
                            printf("%d   %d\n",a,b);
                            scanf("%d    %d",&a,&b);
                            printf("%d   %d\n",a,b);
                        }
                        此时我们在终端输入两个数,这两个数无论中间隔多少个空格,a,b都能接收
                        但是如果我们用%c去获取一个字符的话,规则就会有所不同:
                        int main()
                        {
                            char  a,b;
                            scanf("%c%c",&a,&b);
                            printf("%c   %c\n",a,b);
                        }
                        此时输入格式严格的按照scanf里面的字符串的格式输入,scanf函数可以接收空格,不会把空格当成
                        间隔符。
            
                2. 非转义字符(普通字符,除空白符和%以外的字符)
                    精准匹配,原样输入
                    int main()
                        {
                            int  a,b;
                            printf("%d   %d\n",a,b);
                            scanf("%d+,%d",&a,&b);
                            printf("%d   %d\n",a,b);
                        }
                3. 转义字符(以%开头的字符),有特殊的含义:
                    %d --> [0-9]+
                    %c --> 匹配一个字符
                    %f --> 浮点数
                ####    %s --> 字符串(不带空白符,scanf把空白符当成间隔符)
                其它的参数为第二类,地址列表:
                    格式化字符串中一个转义字符会对应一个地址,把一个转义字符的输入存储到指定的地址中去。
                如果转义字符的个数大于地址个数,程序行为会认为undefined(未定义的)。 
                例子: 
                    int a;
                    char name[32];
                    scanf("%d%s%d",&a,name);
                    printf("%d %s",a,name);
                结果会报段错误
                
                scanf输入如何结束?
                我们知道scanf从stdin的读缓冲区中获取输入。
                scanf结束输入的条件有两个:
                    1.该输入完已经输入了
                        scanf("abcd%d  %cabcd",&a,&c);
                        前面的输入格式严格输入,如果你的输入参数提前已经输入完毕,这个时候可以提前结束输入(\n)
                    
                    2.匹配失败
                        上面的例子中,比如我们输入: Abcd,这个时候已经匹配失败,其实scanf函数停止匹配
                scanf函数的返回值为成功匹配的变量个数
                练习: 
                    int a;
                    char c;
                    int r = scanf("abcd%d  %c",&a,&c);
                    假设用户输入: 
                        ABCD123 A 
                    a,r,c的值分别是多少?
                        a,c是一个未知值  r就等于0
                    假设用户输入: 
                        abcd123\n 
                    此时会是什么现象?
                        此时终端为正在等待输入,因为前面的abcd匹配成功,123被保存到了a变量,此时缓冲区里还剩一个\n
                    那么\n就会被过滤掉,此时c的值还没接收,所以scanf函数会继续等待
                    
            2)int sscanf(const char *str, const char *format, ...);
                sscanf函数的功能和返回值跟scanf函数一样,只不过,sscanf的输入来源不是stdin,而是str指向的字符串。
                sscanf的参数有三类: 
                    1. str是输入来源字符串
                    2. format是格式化字符串
                    3. 其它参数就是地址列表
                例子: 
                    (1) 
                        char *str = "1234BCDDDDDD";
                        int r,a;
                        char c;
                        r = sscanf(str,"%d %c",&a,&c);
                        r=2  a=1234  c='B'
                    (2)char *s = "1234";
                        int a;
                        sscanf(s,"%d",&a);
                        a=1234 
            
            3)int fscanf(FILE *stream, const char *format, ...);
                fscanf的功能和返回值跟scanf也是一样,但是fscanf的输入来源是stream指定的文件流
                所以fscanf的参数分成三类:
                1. 第一个参数stream  指定输入来源
                2. 第二个参数 格式化字符串format
                3. 其它参数为地址列表
                scanf(...)<==>fscanf(stdin,...)
        练习: 
            假设此时有一个文件 stu.txt 
                1 zhangsan 83
                2 lisi 90
                .....
            将这些学生中成绩最高的那个人的所属的那一行全部打印 -->fscanf
                struct student
                {
                    int num;
                    char name[32];
                    int score;
                };
            
        b. 格式化输出  


        printf/sprintf/snprintf/fprintf
        "格式化输出":按照特定的格式进行输出
        1)printf 
            int printf(const char *format, ...);
            printf可以带很多个参数,参数分成两类:
            第一个参数为格式化字符串
                格式化字符串有两类字符:
                1. 转义字符 
                    %d -->按十进制有符号整数输出
                    %u %ld %lu
                    %f %g %e
                    %f 就是以小数的形式输出实型数据,整数部分按原样输出,小数点后面输出6位
                    %e 以指数的形式输出实型的数据,格式: 
                         *.******e+/-**
                    %g 在输出的时候会自动选择两种格式输出时宽度比较小的那种格式输出,而且不输出无意义的零。
                        例子:  
                            printf("%f %e %g\n",1234.5,1234.5,1234.5);
                            输出:    1234.500000 1.234500e+03 1234.5
                    %c 
                    %s 
                    ....
                2.非转义字符:原样输出。
            其他参数就是对象或者变量的列表
            注意:要输出的变量或者对象的个数应该与转义字符保持一致 
            返回值:实际上打印的字符的个数。
                例子: 
                    int a =123;
                    char c='B';
                    int r = printf("a=%d c=%c\n",a,c);//a=123 c=B
                    printf("r==%d\n",r);//r=10
        2) fprintf 
            int fprintf(FILE *stream, const char *format, ...);
            fprintf的功能和返回值与printf一样,只不过fprintf输出不是输出到stdout,而是输出到stream中。
            fprintf的参数三种: 
                1. stream 指定输出到的文件流
                2. format 格式化字符串 
                3. 要输出的变量列表
            返回值:为实际上输出到文件中的字符个数
            printf(format,..)<==>fprintf(stdout,format,...)
        3) sprintf
              int sprintf(char *str, const char *format, ...);
                sprintf的功能和返回值与printf一样,只不过sprintf输出不是输出到stdout,而是输出到str指定的内存中。
                sprintf的参数三种: 
                    1. str 内存地址 
                    2. format 格式化字符串 
                    3. 要输出的变量列表
            返回值:为实际上输出到内存中的字符个数
            例子: 
                1: 
                char pathname[512];
                 char *path="/mnt/hgfs/share";
                 char *name="1.mp3"
                 sprintf(pathname,"%s/%s",path,name);
                 printf("%s\n",pathname);
                 return 0;
                2: 
                 如果我想连续追加的形式往一个内存空间中写数据: 
                    int r=0;
                    char pathname[512];
                    char *name1="/mnt/hgfs/share";
                    char *path="music";
                    char *name2="1.mp3";
                    r+=sprintf(pathname+r,"%s",name1);
                    r+=sprintf(pathname+r,"/%s",path);
                    r+=sprintf(pathname+r,"/%s",name2);
                    ....
                    printf("%s\n",pathname);
                    
            但是,sprintf函数有bug,它不会考虑str指定的空间是否够用,所以也有越界的风险-->"内存的非法访问"
            
        4)snprintf 
            int snprintf(char *str, size_t size, const char *format, ...);
            snprintf中size指定str指向的空间中最大的可用长度
            也就是说,格式化输出顶多输出size-1个字符到str指向的空间中去。
            例子: 
                char s[8];
                char *str="123456789";
                int r = snprintf(s,sizeof(s),"%s",str);
                printf("s:%s\n",s);
                printf("r = %d\n",r);
            snprintf的返回值是应该要输出到内存空间的字符串的长度,而不是实际上输出到内存的字符串的长度     


    拓展知识:


        1.
        我们可以通过一个指令md5sum用来验证两个文件是否一致,
        md5sum:
            在网络传输,设备转存,复制文件的时候,可能会出现传输文件前后不一致。那么一旦传输出错,文件完整性就不能
        保证。这个时候用指令对文件进行完整性验证
            在linux下我们可以用md5sum进行校验。它会根据文件中的内容生成一个32位的16进制值(md5值),如果有两个文件
        的md5值是完全一样-->内容完全相同
        指令: 
            md5sum 1.txt 2.txt 
        -------------------------------------------
        2. 
          获取当前进程的工作目录
          a.getwd 
            NAME
           getcwd, getwd, get_current_dir_name - get current working directory
            
            SYNOPSIS
                   #include <unistd.h>
                getwd用来获取当前的工作目录,并且把路径保存在buf指定的空间中
                char *getwd(char *buf);
                    @buf:用来保存工作路径
            返回值: 
                成功的话,返回buf
                失败返回NULL,同时errno被设置
            例子: 
                int main()
                {
                    char buf[64];
                    char *p = getwd(buf);
                    if(p == NULL)
                    {
                        perror("getwd failed");
                        return -1;
                    }
                    printf("buf:%s\n",buf);
                    printf("p:%s\n",p);
                }
            生成后的可执行文件如果在不同的路径下运行,结果不一样
            但是getwd这个函数也会有警告,编译会出现提示:原因也是因为要保存的路径有可能会超过buf指向的空间
            
        b. char *getcwd(char *buf, size_t size);
                @buf:指向的空间用来保存当前的工作路径
                @size: 指定buf指向的空间最大的可用长度,如果当前工作路径的长度>你设定的size-1,这个函数就会报错
        返回值: 
            成功的话,返回当前的目录字符串的地址
            失败的话返回NULL,同时errno被设置
            int main()
            {
                char buf[12];
                char *p = getcwd(buf,12);
                if(p == NULL)
                {
                    perror("getcwd failed");
                    return -1;
                }
                printf("buf:%s\n",buf);
                printf("p:%s\n",p);
            }
            输出结果:getcwd failed: Numerical result out of range
            将buf中12改成足够大
        
        c.  char *get_current_dir_name(void);
            也是用来获取进程当前的工作目录的绝对路径,只不过,这个函数在内部自动malloc足够长的空间用来保存工作目录
        并且返回这个首地址,所以,为了防止内存的泄露,调用者在使用完这个函数之后,记得free掉这个空间
            返回值: 
                成功的话,返回首地址
                失败的话返回NULL,同时errno被设置
            例子:
            #define _GNU_SOURCE //因为这个函数是GNU拓展的函数 这个宏放在头文件的上面

            #include <stdio.h>
            #include <string.h>
            #include <unistd.h>
            #include <stdlib.h>            
            int main()
            {
                char *p = get_current_dir_name();
                if(p == NULL)
                {
                    perror("getcwd failed");
                    return -1;
                }
                printf("p:%s\n",p);
                free(p);
                p=NULL;
                return 0;
            }
        3. 改变进程的当前工作目录
            chdir 
            NAME
       chdir, fchdir - change working directory
             
        SYNOPSIS
               #include <unistd.h>

        int chdir(const char *path);
        int fchdir(int fd);

            path是要切换的工作目录
                如: "home/china"
            
            fd就是你要切换的工作目录的文件描述符
                如:  
                    int fd = open("home/china",O_RDWR);
                    fchdir(fd);
        返回值: 
            成功返回0,失败返回-1,同时errno被设置 
            #define _GNU_SOURCE //因为这个函数是GNU拓展的函数 这个宏放在头文件的上面

            #include <stdio.h>
            #include <string.h>
            #include <unistd.h>
            #include <stdlib.h>            
            int main()
            {
                char *p = get_current_dir_name();
                if(p == NULL)
                {
                    perror("getcwd failed");
                    return -1;
                }
                printf("p:%s\n",p);
                free(p);
                p=NULL;
                
                if(chdir("/home/china"))
                {
                    perror("chdir failed");
                    return -1;
                }
                p=get_current_dir_name();
                printf("p:%s\n",p);
                free(p);
                return 0;
            }
                
                
               

流是计算机科学中的一个重要概念,它用于表示一系列连续的数据元素,按照顺序进行访问和处理。

流的概念通常用于处理输入和输出操作,并提供了一种灵活而高效的方式来处理大量数据或连续生成的数据。

流的概念常见于文件读写、网络通信、管道通信、数据流处理等场景中。在编程语言中,通常会提供相应的流类或相关的API来支持流操作,使得程序员可以以流式的方式高效处理数据。

【可以简单的把流理解成为连续有序的数据】

【比如你的文件操作,默认都是以流的方式存放数据的】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值