美文网首页
C/C++文件读写

C/C++文件读写

作者: decoding | 来源:发表于2017-01-15 21:17 被阅读0次

    简介

    C/C++中可以使用以fopen、fclose为代表的文件操作函数对文件进行读写。

    注:本文在Linux平台进行演示。(Ubuntu 12.04 LTS + gcc version 4.6.3)

    fopen/ fclose

    在C中,对文件的操作套路都是先用fopen打开文件,做些读写的操作,然后关闭文件。

    FILE *fopen(const char *path, const char *mode);

    • path 指文件路径及文件名
    • mode 打开模式,主要集中在r w a + b t这几种模式组合
    • 返回值:失败则返回NULL,成功返回FILE指针

    int fclose(FILE *fp);

    • fp 为打开的文件指针
    • 返回值成功返回0,失败返回EOF(多为-1)

    b t 这两种模式,指二进制模式和文本模式,在Linux平台下并没有任何区别。在windows平台下对文本模式的换行符\n会处理为\r\n。

    常用模式说明如下

    mode description
    r 只读方式打开,文件必须存在否则失败
    r+ 读写方式打开,文件必须存在否则失败
    rb+ 读写方式打开二进制文件,文件必须存在否则失败
    w 只写方式打开,文件不存在则新建,存在则清空文件内容
    w+ 读写方式打开,文件不存在则新建,存在则清空文件内容
    a 只写方式打开,文件不存在则新建,存在则写入的内容追加到文件尾部
    a+ 读写方式打开,文件不存在则新建,存在则写入的内容追加到文件尾部

    示例

    我们通过打开当前目录下的data.txt文件,用argv[1]传递打开模式,测试r r+ rb+ 三种模式

    #include <stdio.h>
    #include <stdlib.h>
    
    const char* TEST_FILE_PATH = "./data.txt";
    
    int main(int argc, char **argv)
    {
        if ((2 != argc) || (NULL == argv[1]))
        {
            printf("Please Run As: ./a.out r \n");
            return -1;
        }
    
        FILE *fp = fopen(TEST_FILE_PATH, (const char*)argv[1]);
        if(NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
    
        fclose(fp);
        fp = NULL;
    
        return 0;
    }
    

    编译 g++ test_mode_r.cpp
    当前目录下文件情况

    yyl@Machine:/media/sf_share/lc++/io_file$ ls
    a.out test_mode_r.cpp

    分别用r r+ rb+ 方式打开,若不存在文件则都打开失败

    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out r
    FileName[./data.txt] Mode[r] Open Failed
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out r+
    FileName[./data.txt] Mode[r+] Open Failed
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out rb+
    FileName[./data.txt] Mode[rb+] Open Failed
    yyl@Machine:/media/sf_share/lc++/io_file$

    用w模式打开,使其自动建立文件,再用r三种模式打开则可以成功

    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out w
    yyl@Machine:/media/sf_share/lc++/io_file$ ls
    a.out data.txt test_mode_r.cpp
    yyl@Machine:/media/sf_share/lc++/io_file$ cat data.txt
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out r
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out r+
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out rb+

    对w a模式的理解离不开对文件的读写,这里就先介绍下文件的读写和文件指针位置,然后接着介绍w 模式和 a模式

    fread/fwrite

    fread和fwrite是经典的读写函数,主要功能是将数据从文件读到buf或者从buf写到文件中,常用于二进制文件的读写当中。

    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

    • ptr buf首地址
    • size 读取的每一个数据的大小
    • nmemb 读取多少个数据项(总共读取的大小为 size * nmemb)
    • stream 文件指针
    • 返回值 成功返回真正读取/写入文件的数据项的个数(可以根据此判断读取或者写入的数据是否有效)

    ftell/fseek

    ftell和 fseek 用来获取和文件指针的位置,可以把文件内容看做一个字符串,我们用指针对其进行操作,fseek可以移动到字符串的头、尾等位置

    int fseek(FILE *stream, long offset, int whence);
    long ftell(FILE *stream);

    • stream 文件指针
    • offset whence,以whence为基准移动offset个字节的指针位置。基准位置有(SEEK_SET/SEEK_CUR/SEEK_END)分别代表文件首位置和文件尾位置
    • 返回值,成功返回0,失败返回-1

    下面修改测试代码后,我们继续测试w a两种模式打开文件

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef unsigned char BYTE;
    
    const BYTE  MAX_BUF_LEN    = 0xfe;
    const char* TEST_FILE_PATH = "./data.txt";
    
    int main(int argc, char **argv)
    {
        if ((2 != argc) || (NULL == argv[1]))
        {
            printf("Please Run As: ./a.out r \n");
            return -1;
        }
    
        FILE *fp = fopen(TEST_FILE_PATH, (const char*)argv[1]);
        if(NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
        printf("OpenMode[%s] ftell=%ld \n", argv[1], ftell(fp));
    
        char byBuf[MAX_BUF_LEN + 1] = {0};
    
        int iRet = snprintf(byBuf, MAX_BUF_LEN, "ABCDEFG%d",7);
    
        iRet = fwrite((const void*)byBuf, 1, iRet, fp);
        printf("fwrite iRet = %d, strlen byBuf = %d, byBuf = %s,ftell = %ld\n", iRet, strlen(byBuf), byBuf, ftell(fp));
    
        (void)memset(byBuf, 0, MAX_BUF_LEN + 1);
        // (void)fseek(fp, 0, SEEK_SET);
        // (void)fflush(fp);        // 注意这里。我重新rb打开文件,w/wb/wb+读不出来我没读出来。
        fclose(fp);
        fp = NULL;
    
        fp = fopen(TEST_FILE_PATH, "rb");
        if(NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
        printf("OpenMode[%s] ftell=%ld \n", "rb", ftell(fp));
        
        iRet = fread((void*)byBuf, 1, MAX_BUF_LEN, fp);
        printf("fread  iRet = %d, strlen byBuf = %d, byBuf = %s,ftell = %ld\n", iRet, strlen(byBuf), byBuf, ftell(fp));
    
        fclose(fp);
        fp = NULL;
    
        return 0;
    }
    

    w w+ wb+模式,不建议使用w w+ wb+模式进行文件读取,在我机器上测试时发现直接用这三种模式没有读出来,也不知道是不是个例。

    fread iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out w
    OpenMode[w] ftell=0
    fwrite iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    OpenMode[rb] ftell=0
    fread iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out wb
    OpenMode[wb] ftell=0
    fwrite iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    OpenMode[rb] ftell=0
    fread iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out wbwb+
    OpenMode[wbwb+] ftell=0
    fwrite iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    OpenMode[rb] ftell=0

    fread iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out wb+
    OpenMode[wb+] ftell=0
    fwrite iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    OpenMode[rb] ftell=0
    fread iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 8
    yyl@Machine:/media/sf_share/lc++/io_file$

    跟预期基本一致,只是C中并没有对模式进行严格校验,wbwb+ 这种也可以。

    下面我们再看一下a a+ ab+ 这三种模式

    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out a
    OpenMode[a] ftell=8
    fwrite iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 16
    OpenMode[rb] ftell=0
    fread iRet = 16, strlen byBuf = 16, byBuf = ABCDEFG7ABCDEFG7,ftell = 16
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out a+
    OpenMode[a+] ftell=0
    fwrite iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 24
    OpenMode[rb] ftell=0
    fread iRet = 24, strlen byBuf = 24, byBuf = ABCDEFG7ABCDEFG7ABCDEFG7,ftell = 24
    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out ab+
    OpenMode[ab+] ftell=0
    fwrite iRet = 8, strlen byBuf = 8, byBuf = ABCDEFG7,ftell = 32
    OpenMode[rb] ftell=0
    fread iRet = 32, strlen byBuf = 32, byBuf = ABCDEFG7ABCDEFG7ABCDEFG7ABCDEFG7,ftell = 32
    yyl@Machine:/media/sf_share/lc++/io_file$

    可以看到三种模式均可以在尾部写文件,a模式打开后,文件指针直接在文件尾部,而a+ ab+文件指针则在文件首部,我没有使用fseek操作,写的数据也自动加到了文件尾部,而且fwrite后会自动修改文件指针。

    那么这里如果用a模式打开是否可以通过fseek把文件指针移动到文件首部呢?

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef unsigned char BYTE;
    
    const BYTE  MAX_BUF_LEN    = 0xfe;
    const char* TEST_FILE_PATH = "./data.txt";
    
    int main(int argc, char **argv)
    {
        if ((2 != argc) || (NULL == argv[1]))
        {
            printf("Please Run As: ./a.out r \n");
            return -1;
        }
    
        FILE *fp = fopen(TEST_FILE_PATH, (const char*)argv[1]);
        if(NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
        printf("OpenMode[%s] ftell=%ld \n", argv[1], ftell(fp));
    
        int iRet = fseek(fp, 0, SEEK_SET);
        printf("fseek SEEK_SET iRet = %d,ftell=%ld \n", iRet, ftell(fp));
    
        fclose(fp);
        fp = NULL;
    
        return 0;
    }
    

    测试结果

    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out a
    OpenMode[a] ftell=32
    fseek SEEK_SET iRet = 0,ftell=0
    yyl@Machine:/media/sf_share/lc++/io_file$

    从结果中可以看出a模式下也可以通过fseek移动指针。

    通过fopen/fclose、fread/fwrite、ftell/fseek,我们可以完成几乎所有的文件操作,下面再介绍下文件其他的IO函数。

    fgetc/fputc

    int fgetc(FILE *stream);

    • stream 文件指针
    • 返回值: 成功返回所得字符,失败返回EOF(-1)

    int fputc(int c, FILE *stream);

    • c 要写入文件的字符
    • stream 文件指针
    • 返回值: 成功返回写入的字符,失败返回EOF(-1)

    同样写和读分开打开

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef unsigned char BYTE;
    
    const BYTE  MAX_BUF_LEN    = 0xfe;
    const char* TEST_FILE_PATH = "./data.txt";
    
    int main(int argc, char **argv)
    {
        if ((2 != argc) || (NULL == argv[1]))
        {
            printf("Please Run As: ./a.out r \n");
            return -1;
        }
    
        FILE *fp = fopen(TEST_FILE_PATH, "rb");
        if (NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
    
        char ch = 0;
        printf("fgetc:\n");
        while ((ch = fgetc(fp)) != EOF)
        {
            (void)putchar(ch);
        }
        printf("\n");
        fclose(fp);
        fp = NULL;
    
        fp = fopen(TEST_FILE_PATH, (const char*)argv[1]);
        if (NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
    
        (void)fseek(fp, 0, SEEK_SET);
        const char *buf = "Fuck!";
        for (const char *p = buf; *p != '\0'; p++)
        {
            if (*p != fputc(*p, fp))
            {
                printf("fpuc failed ch[%c] \n", *p);
            }
        }
        fclose(fp);
        fp = NULL;
    
        fp = fopen(TEST_FILE_PATH, "rb");
        if (NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
    
        ch = 0;
        printf("fgetc:\n");
        while ((ch = fgetc(fp)) != EOF)
        {
            (void)putchar(ch);
        }
        printf("\n");
    
        fclose(fp);
        fp = NULL;
    
        return 0;
    }
    

    yyl@Machine:/media/sf_share/lc++/io_file$ cat data.txt
    Fuck!yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out w+
    fgetc:
    Fuck!
    fgetc:
    Fuck!
    yyl@Machine:/media/sf_share/lc++/io_file$

    fgets/fputs

    char *fgets(char *s, int size, FILE *stream);

    • s 字符串buf
    • size 读取的大小,最多取size-1个字符,自动添加'\0',遇到\n EOF会提前停止
    • stream 文件指针

    int fputs(const char *s, FILE *stream);

    • fputs() writes the string s to stream, without its terminating null byte ('\0').

    fscanf/fprintf

    文件字符串输出格式化IO,类似scanf和printf。注意其返回值。

    int fscanf(FILE *stream, const char *format, ...);

    • 返回值: 成功则返回读到参数个数,有可能只成功部分参数,注意校验
      int fprintf(FILE *stream, const char *format, ...);
    • 返回值: 成功返回输出的字符个数
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef unsigned char BYTE;
    
    const BYTE  MAX_BUF_LEN    = 0xfe;
    const char* TEST_FILE_PATH = "./data.txt";
    
    int main(int argc, char **argv)
    {
    
        FILE *fp = fopen(TEST_FILE_PATH, "wb+");
        if (NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
    
        int val_a = 23;
        int val_b = 18;
        int iRet = fprintf(fp, "%dDGDG%d", val_a, val_b);
        printf("fprintf iRet = %d\n", iRet);
    
        fclose(fp);
        fp = fopen(TEST_FILE_PATH, "rb+");
        if (NULL == fp)
        {
            printf("FileName[%s] Mode[%s] Open Failed \n", TEST_FILE_PATH, argv[1]);
            return -1;
        }
    
        val_a = 0;
        val_b = 0;
        iRet = fscanf(fp, "%dDGDG%d", &val_a, &val_b);
        printf("iRet = %d, a = %d, b = %d \n", iRet, val_a, val_b);
    
        return 0;
    }
    

    yyl@Machine:/media/sf_share/lc++/io_file$ ./a.out
    fprintf iRet = 8
    iRet = 2, a = 23, b = 18

    相关文章

      网友评论

          本文标题:C/C++文件读写

          本文链接:https://www.haomeiwen.com/subject/sfaybttx.html