014+limou+C语言深入知识——(6)文件操作

001、文件的概念

(1)什么是文件

磁盘上的文件就是文件,在程序设计中,从文件功能角度来看,一般谈到的文件有两种:程序文件、数据文件

(2)文件的分类

①程序文件

比如源程序文件、目标文件、可执行文件

②数据文件

单纯存储/输出数据的文件,比如:存放数字、存放字符等,下面要介绍的函数多是处理数据文件的,数据文件又可以分为文本文件和二进制文件

(3)文件名

一个文件要有一个唯一的文件标识符,便于计算机/用户识别和引用,文件名包含三个部分:文件路径+文件名主干+文件后缀,比如:C:\limou\test\文档.txt(注意windows的文件分隔符是“\”,而Linux是“/”)

002、文件指针

(1)文件指针概念FILE

①在缓存文件系统中,最关键概念是“文件类型指针”,简称“文件指针”

②每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息,这些信息保存在一个结构体中,而文件指针就是为了维护这块空间的指针,因此文件指针其本质就是一个结构体指针

③结构体内部主要记录了文件的相关信息:文件名字、文件状态、文件当前的位置

④如果使用一个指向文件空间的指针,来维护FLILE结构的变量,这样在代码中使用起来就会更加方便

(2)在stdio.h中的文件指针定义

但是不同编译器、系统的文件实现机制可能不太相同,以下是windows环境中VS2013的定义FILE

struct _iobuf {
    char *_ptr;
    int   _cnt;
    char *_base;
    int   _flag;
    int   _file;
    int   _charbuf;
    int   _bufsiz;
    char *_tmpfname;
};
typedef struct _iobuf FILE;

003、文件函数(站在程序文件的角度)

014+limou+C语言深入知识——(6)文件操作_第1张图片

(1)打开文件

FILE* fopen(const char* filename, const char* mode);
  • 打开文件若成功就会返回一个FILE*类型的指针,若出错就会返回一个NULL,因此需要对指针进行检查
  • 有关参数mode的解读和表格总结归纳(站在需要程序文件角度)

014+limou+C语言深入知识——(6)文件操作_第2张图片

(2)关闭文件

int fclose( FILE* stream );
  • 关闭文件并不会把原来指向文件的指针置空,因此需要我们手动置空,避免原有指针成为空指针
//测试代码,我在我的桌面放置了一个文件,文件名为:"C:\Users\DELL\Desktop\limou_cache_file_1.txt"
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
int main()
{
    FILE* pf = fopen("C:\\Users\\DELL\\Desktop\\limou_cache_file_1.txt", "r");
    if (pf == NULL)
    {
        perror("fopen失败!\n");
        return 1;
    }
    fclose(pf);
    pf = NULL;
    return 0;
}

004、文件顺序读写(站在程序文件的角度,请将屏幕和键盘也看做文件)

014+limou+C语言深入知识——(6)文件操作_第3张图片

(1)字符输入函数fgetc(适用于所有输出流)

int fgetc( FILE* stream );
//如果读取成功返回ASCII码值(字符)并且提升为int,以容纳特殊值EOF(其值为-1)
//如果位置指示符位于文件末尾,该函数返回EOF并设置流的EOF指示符(feof)。
//如果发生其他读取错误,该函数也返回EOF,但设置其错误指示符(ferror)。
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
int main()
{
    //打开文件
    FILE* pf = fopen("C:\\Users\\DELL\\Desktop\\limou_cache_file_1.txt", "r+");
    if (pf == NULL)
    {
        perror("fopen失败!\n");
        return 1;
    }

    //读取文件
    int i = 0;
    for (i = 0; i < 26; i++)
    {
        char ch = fgetc(pf);
        printf("%c", ch);
    }

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第4张图片

(2)字符输出函数fputc(适用于所有输出流)

int fputc( int character, FILE* stream );
//如果读取成功返回ASCII码值,并且提升为int,以容纳特殊值
//如果发生写入错误,则返回EOF,并设置错误指示器(ferror)
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
int main()
{
    //打开文件
    FILE* pf = fopen("C:\\Users\\DELL\\Desktop\\limou_cache_file_1.txt", "r+");
    if (pf == NULL)
    {
        perror("fopen失败!\n");
        return 1;
    }

    //写入文件
    int i = 0;
    for (i = 0; i < 26; i++)
    {
        fputc('a' + i, pf);
    }
    
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第5张图片

(3)文本输入函数fgets(适用于所有输出流)

char* fgets ( char* str, int num, FILE* stream );//(字符串数组名, 最大的个数字符, 文件指针)
//如果读取成功,返回指向字符串的指针
//如果在读取字符时遇到文件结束符,则设置eof指示符(feof)。如果这种情况发生在读取任何字符之前,则返回的指针是空指针(str的内容保持不变)
//如果发生读错误,则设置错误指示符(ferror)并返回NULL(但str所指向的内容可能已更改)。
#include 
#include 
int main()
{
    //打开文件
    FILE* pf = fopen("C:\\Users\\DELL\\Desktop\\limou_cache_file_1.txt", "r+");//这个文件里面我又重新放入一段文本了
    if (pf == NULL)
    {
        perror("fopen失败!\n");
        return 1;
    }
    //读取文件
    char arr[150];
    for (int i = 0; i < 10; i++)
    {
        fgets(arr, 150, pf);
        printf("%s\n", arr);
    }
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第6张图片

注意实际上读取num-1个字符,因为保存多一个’\n’

(4)文本输出函数fputs(适用于所有输出流)

int fputs ( const char* str, FILE* stream );
//如果成功,则返回一个非负值
//在错误时,函数返回EOF并设置错误指示符(ferror)
#include 
#include 
int main()
{
    //打开文件
    FILE* pf = fopen("C:\\Users\\DELL\\Desktop\\limou_cache_file_1.txt", "r+");
    if (pf == NULL)
    {
        perror("fopen失败!\n");
        return 1;
    }

    //写入文件
    fputs("hello_limou_welcome_file!", pf);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第7张图片

同时可以看到之前写的数据被替换了,只是没有"w"那么彻底,因此如果多次使用fputs函数会“覆盖显示”在同一行,除非用户输入的字符串本身带有’\n’字符

(5)格式化输入函数fscanf(适用于所有输出流)

int fscanf ( FILE* stream, const char* format, ... );
#include 
int main()
{
    typedef struct S
    {
        int a;
        double b;
        char c[10];
    }S;
    S s = { 0 };
    //打开文件
    FILE* pf = fopen("C:\\Users\\DELL\\Desktop\\limou_cache_file_1.txt", "r+");
    if (pf == NULL)
    {
        perror("fopen失败!\n");
        return 1;
    }

    //读文件
    fscanf(pf, "%d %f %s", &(s.a), &(s.b), &(s.c));//这里最好加上括号,清晰一点

    //打印到屏幕上
    printf("%d %lf %s", s.a, s.b, s.c);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第8张图片

(6)格式化输出函数fprintf(适用于所有输出流)

int fprintf ( FILE* stream, const char* format, ... );
#include 
int main()
{
    //存储输入文件的数据结构体
    typedef struct S
    {
        int a;
        double b;
        char c[10];
    }S;
    S s = { 100, 3.1415926, "limou3434" };

    //打开文件
    FILE* pf = fopen("C:\\Users\\DELL\\Desktop\\limou_cache_file_1.txt", "r+");
    if (pf == NULL)
    {
        perror("fopen失败!\n");
        return 1;
    }

    //写文件
    fprintf(pf, "%d %f %s", s.a, s.b, s.c);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第9张图片

(7)二进制输入fread(适用于文件)

size_t fread ( void* ptr, size_t size, size_t count, FILE* stream );
//返回成功读取的元素总数
//如果这个数字与count参数不同,则要么发生了读取错误,要么在读取时到达了文件末尾。在这两种情况下,都设置了正确的指示器,可以分别使用ferror和feof进行检查。
//如果size或count中有一个为零,函数返回零,流状态和ptr指向的内容都保持不变。
//注意size_t是无符号整型
#include 
typedef struct S
{
    int a;
    double b;
    char arr[10];
}S;
int main()
{
    //输入数据
    S s1 = { 3, 3.14, "abcde" };
    FILE* pf = fopen("limou_txt", "wb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    fwrite(&s1, sizeof(S), 1, pf);
    //关闭文件
    fclose(pf);
    pf = NULL;

    //输出数据
    S s2 = { 0 };
    pf = fopen("limou_txt", "rb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    fread(&s2, sizeof(S), 1, pf);
    printf("%d %f %s\n", s2.a, s2.b, s2.arr);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第10张图片

(8)二进制输出fwrite(适用于文件)

size_t fwrite ( const void* ptr, size_t size, size_t count, FILE* stream );
//返回成功写入的元素总数
//如果此数字与count参数不同,则写入错误阻止函数完成。在这种情况下,将为流设置错误指示器(ferror)
//如果size或count中有一个为零,则函数返回零,错误指示符保持不变
#include 
typedef struct S
{
    int a;
    double b;
    char arr[10];
}S;
int main()
{
    //输入数据
    S s1 = { 3, 3.14, "abcde" };
    FILE* pf = fopen("limou_txt", "wb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    fwrite(&s1, sizeof(S), 1, pf);
    //关闭文件
    fclose(pf);
    pf = NULL;

    //输出数据
    S s2 = { 0 };
    pf = fopen("limou_txt", "rb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    fread(&s2, sizeof(S), 1, pf);
    printf("%d %f %s\n", s2.a, s2.b, s2.arr);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第11张图片

005、区分scanf/printf、fscanf/fprintf、sscanf/sprintf

014+limou+C语言深入知识——(6)文件操作_第12张图片

(1)scanf,针对标准输入流(stdin)的,格式化的输入函数

(2)printf,针对标准输出流(stdout)的,格式化的输出函数

(3)scanf,针对所有输入流的,格式化的输入函数

(4)printf,针对所有输出流的,格式化的输出函数

(5)sscanf,将字符串输入转化为格式化输入

int sscanf ( const char* s, const char* format, ...);
#include 
typedef struct S
{
    int a;
    double b;
    char c[10];
}S;
int main()
{
    S s1 = { 200,3.14f,"limou3434" };

    //转化为字符串数据
    char arr[200] = { 0 };
    sprintf(arr, "%d %lf %s\n", s1.a, s1.b, s1.c);
    printf("%s", arr);

    //转化为格式化数据
    S s2 = { 0 };
    sscanf(arr, "%d %lf %s", &(s2.a), &(s2.b), s2.c);
    printf("%d %lf %s\n", s2.a, s2.b, s2.c);
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第13张图片

(6)sprintf,将格式化输出转化为字符串输出

int sprintf ( char* str, const char* format, ... );
#include 
typedef struct S
{
    int a;
    double b;
    char c[10];
}S;
int main()
{
    S s1 = { 200,3.14f,"limou3434" };

    //转化为字符串数据
    char arr[200] = { 0 };
    sprintf(arr, "%d %lf %s\n", s1.a, s1.b, s1.c);
    printf("%s", arr);

    //转化为格式化数据
    S s2 = { 0 };
    sscanf(arr, "%d %lf %s", &(s2.a), &(s2.b), s2.c);
    printf("%d %lf %s\n", s2.a, s2.b, s2.c);
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第14张图片

006、文件任意读写

014+limou+C语言深入知识——(6)文件操作_第15张图片

(1)fseek

int fseek( FILE* stream, long int offset, int origin );
//(文件指针, 偏移量, 起始位置)根据文件指针的位置和偏移量来定位文件指针
//其中第三个参数的选择有三种:
//①SEEK_SET,即文件开始位置
//②SEEK_CUR,即文件指针的当前位置
//③SEEK_END,即文件末尾位置
#include 
int main()
{
    //输入数据
    FILE* pf = fopen("limou.txt", "r");//文件指针指向a的地址
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //顺序读写
    int ch = fgetc(pf);
    printf("%c", ch);//打印a,读完后文件指针指向b
    ch = fgetc(pf);
    printf("%c", ch);//打印b,读完后文件指针指向c
    ch = fgetc(pf);
    printf("%c", ch);//打印c,读完后文件指针指向d
    ch = fgetc(pf);
    printf("%c", ch);//打印d,读完后文件指针指向e

    //改变偏移量读写
    fseek(pf, -3, SEEK_CUR);
    ch = fgetc(pf);
    printf("%c", ch);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第16张图片

(2)ftell

long int ftell ( FILE* stream );//返回文件指针相对于起始位置的偏移量
#include 
int main()
{
    //输入数据
    FILE* pf = fopen("limou.txt", "r");//文件指针指向a的地址
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //顺序读写
    int ch = fgetc(pf);
    printf("%c", ch);//打印a,读完后文件指针指向b
    ch = fgetc(pf);
    printf("%c", ch);//打印b,读完后文件指针指向c
    ch = fgetc(pf);
    printf("%c", ch);//打印c,读完后文件指针指向d
    ch = fgetc(pf);
    printf("%c", ch);//打印d,读完后文件指针指向e

    //改变偏移量读写
    fseek(pf, -3, SEEK_CUR);
    ch = fgetc(pf);
    printf("%c", ch);

    //继续顺序读写
    ch = fgetc(pf);
    printf("%c\n", ch);//打印d,读完后文件指针指向e

    //返回偏移量
    long int a = ftell(pf);
    printf("%d\n", a);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第17张图片

(3)rewind

void rewind ( FILE* stream );//修改当前的文件指针,指向开头位置
#include 
int main()
{
    //输入数据
    FILE* pf = fopen("limou.txt", "r");//文件指针指向a的地址
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //顺序读写
    int ch = fgetc(pf);
    printf("%c", ch);//打印a,读完后文件指针指向b
    ch = fgetc(pf);
    printf("%c", ch);//打印b,读完后文件指针指向c
    ch = fgetc(pf);
    printf("%c", ch);//打印c,读完后文件指针指向d
    ch = fgetc(pf);
    printf("%c", ch);//打印d,读完后文件指针指向e

    //改变偏移量读写
    fseek(pf, -3, SEEK_CUR);
    ch = fgetc(pf);
    printf("%c", ch);

    //继续顺序读写
    ch = fgetc(pf);
    printf("%c\n", ch);//打印d,读完后文件指针指向e

    //返回偏移量
    long int a = ftell(pf);
    printf("%d\n", a);

    //修改文件指针为开头
    rewind(pf);
    
    //再次返回偏移量
    a = ftell(pf);
    printf("%d\n", a);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

014+limou+C语言深入知识——(6)文件操作_第18张图片

007、数据文件的分类

(1)二进制文件

数据在内存中以二进制的形式存储,如果不加转化的输出到外存就是二进制文件

(2)文本文件

如果对二进制数据加以转换,以ASCII字符的形式存储的文件就是文本文件

(3)要点注意

两种文件只是看待数据的视角不一样,实际的访问速度和存储空间大小不能简单进行对比

008、文件读取结束的判定

(1)文本文件判断读取是否结束

  • 如果是fgetc判断返回值是否为EOF
  • 如果是fgets判断返回值是否NULL

(2)二进制文件判断读取是否结束

  • 如果是fread判断返回值是否小于实际要读的个数

(3)判断结束的类型:feof和ferror

int feof ( FILE* stream );
  • 注意,在文件的读取过程中,不能用feof的返回值来直接判断文件是否结束!!!
  • feof的作用是,当文件读取结束后,判断读取结束的原因是否是“遇到文件尾结束”(也就是,feof是个“马后炮”)
int ferror ( FILE* stream );
  • feof的作用是,判断读取结束的原因是否是“遇到错误结束”

(4)具体代码

#include 
int main()
{
    //输入数据
    FILE* pf = fopen("limou.txt", "r");//文件指针指向a的地址
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch = 0;
    while ((ch = fgetc(pf)) != EOF)
    {
        printf("%c", ch);
    }
    //通过feof函数来判断是不是遇到文件末尾结束的
    if (feof(pf))
    {
        printf("到文件结尾\n");
    }
    else if (ferror(pf))
    {
        printf("文件读取错误\n");
    }
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}
#include 
enum 
{ 
    SIZE = 5
};
int main() 
{
    //创建数据
    double a[SIZE] = {1.0, 2.0, 3.0, 4.0, 5.0};
    FILE *fp = fopen("test.bin", "wb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    //写入文档
    fwrite(a, sizeof *a, SIZE, fp);

    //关闭文件
    fclose(fp);

    //创建存储数据的数组
    double b[SIZE];

    //打开文件
    fp = fopen("test.bin","rb");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    //读取文件到b数组
    size_t ret_code = fread(b, sizeof* b, SIZE, fp);
    if(ret_code == SIZE) 
    {
        printf("完全读取成功\n");
        for(int n = 0; n < SIZE; ++n) 
            printf("%f ", b[n]);
        putchar('\n');
    } 
    else 
    {
        //如果程序到这里,说明读取异常了
        if (feof(fp))//查看是否遇到末尾
        {
            printf("文件意外结束\n");
        }
        else if (ferror(fp))//查看是否读取错误,这里还需要判断,所以写出else if
        {
            perror("读取错误\n");
        }
    }
    //关闭文件
    fclose(fp);
}

009、文件缓冲区

ANSIC标准采用“缓冲文件系统”处理的数据文件

(1)缓存文件系统的概念

014+limou+C语言深入知识——(6)文件操作_第19张图片

  • 指系统自动地在内存中为程序中,每一个正在使用的文件开辟一块“文件缓冲区”
    • 从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才被一起送到磁盘上
    • 从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区,充满缓冲区后再从缓冲区逐个地将数据送到程序数据区(程序变量等)
  • 缓冲区的大小要根据C编译系统决定的
  • 缓冲区是为了提高运行效率而存在的

(2)验证缓冲区代码

#include 
#include 
int main()
{
    //打开文件
    FILE* pf = fopen("limou.txt", "w");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    //输入数据
    fputs("hello limou!", pf);//先将代码放在输出缓冲区

    //查看文档这个时候是否存储数据
    Sleep(15000);//程序停止运行15秒,这个时候可以打开limou.txt文件来看看是否将数据存入了。可以看到,文件并没有存储数据,因为这段数据还在缓存区域中

    //刷新缓冲
    fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)注意,fflush在高版本的VS上不能使用了,除了这个函数还有其他的方法可以清空缓存区

    //查看文档这个时候是否存储数据
    Sleep(10000);

    //关闭文件
    fclose(pf);//注意,fclose在关闭文件的时候,也会刷新缓冲区
    pf = NULL;
    return 0; 
}

010、额外学习补充

在上面的某一段代码中出现了两个宏

①“宏EXIT_FAILURE”(值为1,即失败)

②“宏EXIT_SUCCESS”(值为0,即成功)

你可能感兴趣的:(C语言学习笔记,c语言,linux)