zl程序教程

您现在的位置是:首页 >  其他

当前栏目

C语言进阶——文件操作(下)

2023-03-14 23:00:32 时间

🌲随机读写

 随机读写函数,需要配合上面的输入输出函数使用,所谓的随机读写,是指通过改变文件指针的偏移量,来写入或读取数据。介绍三个和随机读取有关的函数:fseek 改变文件指针偏移量、ftell 查看当前文件指针的偏移量、rewind 使文件指针复原至起始位置。


🌱fseek

392ac02bc1ca4a6f972d4ebac5b53c75.png

//fseek,文件指针偏移量
int main()
{
    FILE* fp = fopen("test.txt", "w");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    char* pc = "abc";
    fseek(fp, 20, SEEK_SET);//从起点往后偏移
    fputs(pc, fp);
 
    fclose(fp);
    fp = NULL;
    return 0;
}

4228fe1b25fd44ffb1d822d3ff353b8f.png

🌱ftell

98415911f6a64402b87eea2c050d5333.png

//ftell,返回当前文件指针偏移信息
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror(fp);
        return 1;
    }
    printf("当前文件指针偏移量为:%d
", ftell(fp));
    fseek(fp, 20, SEEK_SET);//向后偏移20
    printf("经过fseek设置后的文件指针偏移量为:%d
", ftell(fp));
    fclose(fp);
    fp = NULL;
    return 0;
}

3e7f5bdf38a64db8af718364dfe325ea.png


🌱rewind

214e3a480d274ab3ae2753e5e46f8608.png

//rewind,使文件指针恢复至原位置
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    fseek(fp, 20, SEEK_SET);//先让文件指针向后偏移20
    printf("当前文件指针偏移量为:%d
", ftell(fp));
    rewind(fp);//使文件指针恢复至起始位置
    printf("经过恢复后的文件指针偏移量为:%d
", ftell(fp));
    return 0;
}

094de6266e5448829010da45bba5e958.png

🌱fseek、ftell、rewind 三合一

//fseek、ftell、rewind三合一
//假设文件中存储数据为abcdef
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
    printf("现在文件中内容为abcdef,我们要依次取出e、b、d
");
    fseek(fp, -2, SEEK_END);//从后往前偏移
    printf("先取出字符%c
", fgetc(fp));
    rewind(fp);//还原至起始位置
    fseek(fp, 1, SEEK_SET);//从前往后偏移
    printf("再取出字符%c
", fgetc(fp));
    fseek(fp, 1, SEEK_CUR);//从当前位置向后偏移
    printf("最后再取出字符%c
", fgetc(fp));
 
    fclose(fp);
    fp = NULL;
    return 0;
}

da3798d48acd4256b33ba2b9cdadef7c.png

注意:


每进行一次文件输入输出操作,文件指针都会向后移动一位。比如上面的三合一, 当我们读取到字符 'b' 后,文件指针向后移动一位,指向字符 'c' ,此时只需要把文件指针向后偏移一位,就能愉快的读取到字符 'd' 了。

🌲文本文件与二进制文件

🌱文本文件

 文本文件指以ASCII码(文本方式)存储的数据,原始数据机器能直接看懂,将内存中的数据对应ASCII码解码存储后,我们人类也能看懂,举个栗子,在记事本中写的文本,就是文本文件

4f5aeb6f241349449164407bef4e5e9d.png

🌱二进制文件

 二进制文件是将数据编译后转成二进制形式,然后直接存储的文件,这种文件机器能秒懂,读取效率很高(因为不需要转译),但二进制一般人是看不懂的,部分二进制数据也无法通过ASCII码解码为正确的数据,因此强行输出二进制文件,极有可能会得到乱码。比如将上面的那段话通过二进制形式写入文件中,可以看到除字符类型数外,其他类型的数据变成了乱码。

b47ca223bee94514b54b7c4adc13d1b8.png

 下图为上面的二进制文件在内存中以二进制形式存储的样子,显示为十六进制(节省空间),实际为二进制。

a1414be9d013406a8f6d38811237c080.png


🌱注意

如果待读取的文件中存储的是二进制数据,就需要使用 二进制读取 "rb" 的形式读取数据;反之如果想写入二进制数据,就需要用 二进制写入 "wb" ,无论是二进制还是普通文本,计算机都能读懂,只是我们看不得罢了。小技巧:可以使用二进制存储重要数据,这样外行人一时半会也理解不了。

🌲文件使用注意事项

🌱被错误使用的feof

 很多人在写C语言课设的时候(学生信息管理系统、通讯录系统等),会通过 feof 来判断文件是否读取结束,这是一种错误的用法,因为 feof 的作用是判断当前文件读取结束原因的,如果是因为读取到了末尾而结束,feof(fp) 就为真;除了这个以外,还有另一个文件读取结算原因判断函数,ferror ,当 ferror(fp) 为真时,说明此时发生了读取异常,并非正常结束,我们可以通过这两个报错函数来判断文件读取结束的真正原因。

f4147ce229904dff8b799346b859e044.png

    char arr[100] = "0";
    fgets(arr, sizeof(arr), fp);
    printf("%s
", arr);
    int n = 0;
    if ((n = feof(fp)))
        printf("End by EOF
");
    if (ferror(fp))
        printf("End by IO Error
");

593962f13b2c46beab081b02af81f3f8.png

🌱文件读取结束原因判断

 既然 feof 不是用来判断读取是否结束的,那说明存在其他判断方法,其实答案就是函数设计中,前辈在设计函数时已经考虑好了,比如 fgetc 没有读取到数据会返回EOF,fgets 没有读取到数据会返回NULL,fscanf 可以通过其返回的实际读取元素个数进行判断,fread 可以通过返回值与指定读取的元素数比较。每种读取函数都有属于的自己的判断方法,比如下面这两个例子:


🪴对文本数据进行读取


//读取错误信息判断
//1.文本文件版,假设文件内已有信息,为abcdef
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (NULL == fp)
    {
        perror("fopen::test.txt");
        return 1;
    }
 
    int ch = 0;//接收读取的数据,要用整型,因为EOF为-1
    while ((ch = fgetc(fp)) != EOF)
    {
        printf("%c", ch);
    }
    //判断是为何结束
    if (feof(fp))
        printf("
End by EOF(因读到文件末尾而结束)
");
    else if (ferror(fp))
        printf("
End by IO(因中途读取失败而结束)
");
 
    fclose(fp);
    fp = NULL;
    return 0;
}

b8727d797e3941fc94efb8a28f446a73.png

🪴对二进制文件进行读取

//2.二进制文件版
enum { SIZE = 5 };//相当于宏定义
int main()
{
    double a[SIZE] = { 1.1,2.2,3.3,4.4,5.5 };
    //首先把五个浮点数以二进制的形式,写入文件中
    FILE* fp = fopen("testFoBin.txt", "wb");
    if (NULL == fp)
    {
        perror(fp);
        return 1;
    }
    fwrite(&a, sizeof(*a), SIZE, fp);
    fclose(fp);//写入完成,关闭文件
 
    double b[SIZE] = { 0.0 };
    //现在以二进制的形式读取数据
    fp = fopen("testFoBin.txt", "rb");
    int size = fread(&b, sizeof(*b), SIZE, fp);
    if (size == SIZE)
    {
        printf("No Error!
");
        int i = 0;
        while (i < size)
            printf("%.2lf ", b[i++]);
    }
    else
    {
        if (feof(fp))
            printf("
Error by EOF
");
        else if(ferror(fp))
            printf("
Error by IO
");
    }
    fclose(fp);
    fp = NULL;
    return 0;
}

0148d87ed5a94cc9ad7a09b88f1095f7.png


🌱文件缓冲区

 ANSIC 标准定义了“缓冲文件系统”这个概念,所谓缓冲文件系统是指系统自动地在内存中为程序

中每一个正在使用的文件开辟一块“文件缓冲区”。无论是读取还是写入数据时,都会先将数据送入文件缓冲区,等文件缓冲区装满或遇到刷新指令后,数据才会被读取(写入)到目标空间中。文件缓冲区的大小是由编译器决定的。


af76c11477d04b00810ef185e836f8ac.png

🪴验证文件缓冲区是否存在


我们可以利用睡眠函数 Sleep 来使程序暂停,此时数据还没有被写入文件中,仍然位于缓冲区;之后再手动刷新缓冲区,数据此时会被推送至文件中。

//文件缓冲区
#include<windows.h>
int main()
{
    //打开文件
    FILE* fp = fopen("test.txt", "w");
    if (NULL == fp)
    {
        perror(fp);
        return 1;
    }
    char* ps = "测试文件缓冲区";
    fputs(ps, fp);//先将数据写到缓冲区中
    printf("数据现在已经在缓冲区里面了,但还没有推送到文件中
");
    printf("程序睡眠10秒,10秒后刷新缓冲区
");
    Sleep(10000);//睡眠函数,单位是毫秒
    fflush(fp);
    printf("现在缓冲区已经刷新,数据已经写入文件中了
");
    Sleep(10000);
 
    //关闭文件,当文件关闭时,缓冲区也会被刷新
    fclose(fp);
    fp = NULL;
    return 0;
}


c5091e4d7019484b89a0e23bfc2b7158.png


5131b420b0a3497aa1c89c918017be48.png


4aa9a5bffa5341fc847ba2e1357e5a81.png

 可以看到文件缓冲区是真实存在的。


注意:


fclose 关闭文件后,会自动刷新缓冲区,数据能够推送至文件

当程序运行结束后,缓冲区也会被自动刷新

scanf 遇到 也会触发缓冲区刷新,另外如果其在读取字符型数据时,遇到空白字符(空格、TAB键)也会触发缓冲区的刷新

🌳总结

 以上就是C语言文件操作的所有内容了,从文件的打开到文件的关闭,中间可以进行多种操作,构造出巧妙的数据。当然前提是我们得学会文件的相关操作,可以巧记为单字符读写、行读写、格式化读写和二进制读写,无论是那种操作,都需要和对应的文件操作指令匹配上;关于随机读写,记住那三个偏移量函数就行了;最后需要对文件缓冲区有一定的理解,确保数据能成功推送至文件内。总之,文件操作的学习可以宣布毕业了。

9b70c77871654752a443ff48e10cb29b.jpg


 如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正