当前位置: 首页 > news >正文

C语言-第九章-加餐:文件位置指示器与二进制读写

传送门:C语言-第九章:文件读写

目录

第一节:文件位置指示器

        1-1.fseek 函数

        1-2.ftell 函数

        1-3.rewind 函数

        1-4.案例

第二节:文件的二进制读写

        2-1.文件的二进制打开

        2-2.fread 二进制读函数

        2-3.fwrite 二进制写函数

        2-4.图片读写

第三节:printf 与 fprintf、scanf 与 fscanf的联系

        3-1.printf 与 fprintf

        3-2.scanf 与 fscanf

下期预告:


第一节:文件位置指示器

        文件位置指示器是下一次操作要对文件进行操作的位置(读或写),可以理解为光标的位置。

        进行读操作和写操作时,指示器的初始位置都在文件开头;

        进行追加操作时,指示器的位置在文件结尾。

        进行读写操作时,指示器的位置也会随之后移,我们也可以用用一些函数改变文件指示器的位置。

        1-1.fseek 函数

        fseek 函数根据一个起始位置,将指示器偏移到对应位置,函数原型如下:

        stream:要改变指示器位置的文件

        offset:相对于 origin 位置的偏移量,设置0就表示指示器指向 origin

        origin:起始位置,有三种选项:

                ①SEEK_CUR :指示器当前的位置

                ②SEEK_END:文件结尾

                ③SEEK_SET :文件开头

        返回值:如果设置成功,它将返回0;否则返回非0值

        1-2.ftell 函数

        ftell 函数将返回指示器相对于文件开头位置的偏移量,函数原型如下:

        stream:返回此文件的指示器偏移量

        返回值:成功后。将返回偏移量;否则返回-1

        1-3.rewind 函数

        让指示器返回到文件开头,函数原型如下:

        stream:让stream的指示器指向文件开头

        1-4.案例

        下面是一个案例:得到一个文件的大小并读取文件中的所有内容。

        我们先自定义好一份文件中的内容,随便在网上复制一些内容:

        完整代码如下: 

#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen(".\\text.txt", "r"); // 以读方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}// 让指示器指向文件结尾fseek(pf,0,SEEK_END);// 得到当前位置(文件结尾)相对于文件开头的偏移量int size = ftell(pf); // 这个偏移量就是文件内容的总字节大小// 将指示器复位(重新指向文件开头),以便之后的读取rewind(pf);// 开辟一块空间,用于存放读取的文件内容,注意'\0'也要计算大小char* content = (char*)malloc(size+1);// 读取文件内容fgets(content,size+1,pf);// 打印contentprintf("%s\n",content);return 0;
}

第二节:文件的二进制读写

        为什么要有二进制读写?

        

文件之所以要有二进制读写方式,主要是基于以下几个原因:

  1. 精确控制数据
    二进制读写允许你以字节为单位精确地控制数据的读写。在处理非文本文件(如图片、视频、音频文件等)时,这些文件中的数据是以二进制形式(即0和1的组合)存储的。使用二进制读写可以直接处理这些字节数据,而无需进行任何形式的转换或解释。

  2. 性能优化
    对于大文件或需要高速读写的应用场景,二进制读写通常比文本读写更高效。文本读写可能需要将二进制数据转换为特定的字符编码(如UTF-8),并在读写时进行转换,这会增加额外的处理时间。而二进制读写则直接操作字节,避免了这些额外的开销。

  3. 兼容性和移植性
    在处理跨平台或跨语言的文件时,二进制读写可以保证数据的精确性和一致性。由于文本文件可能受到不同平台字符编码和换行符差异的影响,使用二进制读写可以避免这些问题,确保数据在不同平台和语言之间的一致性和可移植性。

  4. 处理复杂数据结构
    在序列化复杂数据结构(如对象、数组等)到文件时,二进制格式提供了一种更加紧凑和高效的方式来存储这些结构。通过将数据结构转换为二进制表示,并在需要时重新恢复为原始结构,可以实现快速的数据交换和存储。

  5. 直接操作底层数据
    在需要直接操作硬件或系统底层数据的场景中(如驱动程序开发、嵌入式系统开发等),二进制读写是必不可少的。这些场景要求直接读写设备的内存地址或寄存器,而这些数据通常是以二进制形式存在的。

以上内容来自文心一言

        二进制操作如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen(".\\text.txt", "w+b"); // 以二进制读写方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}// 以二进制形式存储数据char put[] = "你好,世界,hello,world";fwrite(put, sizeof(put), 1, pf);// 指示器复位rewind(pf);// 读取二进制内容char get[sizeof(put)];fread(get, sizeof(put), 1, pf);// 打印数据printf("%s\n", get);return 0;
}

        文件中的中文变成了一些看不懂的符号,但是读取回来的内容与之前相同。说明vs编译器和txt文件对英文字符的编码格式相同,对中文的编码格式不同。

        接下来我们简单介绍一下二进制的读写操作:

        2-1.文件的二进制打开

                ①rb:二进制读

                ②wb:二进制写

                ③ab:二进制追加

                ④r+b:二进制读写,不清空文件内容

                ⑤w+b:二进制读写,要清空文件内容

                ⑥a+b:二进制追加+读

        2-2.fread 二进制读函数

        函数原型如下:

        ptr:存放读取到的内容的空间

        size:一次读取的字节数(一个元素的大小)

        count: 读取次数(元素个数)

        stream:被读取的文件

        返回值:返回成功读取的元素的个数;读取失败返回值小于count,如果因为文件内容少,读到文件结尾返回值也会小于count,需要用 feof 函数判断文件是不是正常结束的。

        feof 函数的用法如下:

int feof(FILE* stream); // 正常结束返回真,否则返回假

        2-3.fwrite 二进制写函数

        函数原型如下:

        ptr:存放将写入的内容的空间

        size:一次写入的字节数(一个元素的大小)

        count: 写入次数(元素个数)

        stream:被写入的文件

        返回值:返回成功写入的元素的个数;写入时如果发生错误则返回值小于count。

        2-4.图片读写

        图片在文件中是以二进制的形式存储的,我们可以用二进制读写来操作图片文件,比如将一份图片进行复制,具体做法如下:

        首先在网上随便找一张图片放在代码文件夹下,然后自己创建一个副本,后缀要一致:

 

         此时这个副本里是没有内容的,完整代码如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf1 = fopen(".\\山水风景.png", "r+b"); // 以二进制读方式打开原图片FILE* pf2 = fopen(".\\山水风景副本.png", "w+b"); // 以二进制写方式打开副本图片if (pf1 == NULL || pf2 == NULL){perror("文件打开失败");return 0;}// 让指示器指向文件结尾fseek(pf1, 0, SEEK_END);// 得到当前位置(文件结尾)相对于文件开头的偏移量(文件大小)int size = ftell(pf1);// 开辟一块空间,用于存放读取的文件内容char* content = (char*)malloc(size);// 返回文件开头rewind(pf1);// 读取内容fread(content, size, 1, pf1);// 写入内容fwrite(content, size, 1, pf2);return 0;
}

        执行上述代码,就得到一个副本图片了:

第三节:printf 与 fprintf、scanf 与 fscanf的联系

        3-1.printf 与 fprintf

        printf 函数实际上是 fprintf 函数的一种特殊情况:printf 函数的本质是向屏幕文件写入数据,而 fprintf 函数是向任意文件(包括屏幕文件)写入数据。

        我们知道,一个文件在被操作时先要要用 fopen 打开这个文件,但是 printf 函数似乎不需要打开屏幕文件,就可以向屏幕输出数据。真实情况是屏幕文件在程序执行时是被默认打开的,不需要我们打开。

        既然屏幕文件默认是打开的,那么它有没有文件指针指向它呢?答案是肯定的,这个文件指针是一个来自C语言标准库的外部变量 stdout,它包含在<stdio.h>文件中。

        我们可以使用 stdout + fprintf 向屏幕打印数据:

#include <stdio.h>
#include <stdlib.h>
int main()
{char arr[] = "Hello,world";fprintf(stdout,"%s\n",arr); // 与下面条语句等价printf("%s\n", arr);return 0;
}

 

        屏幕文件的本质:

        屏幕文件本质是一块缓冲区,当缓冲区的内容满足一定条件时,缓冲区的内容才会输出到屏幕上,有以下3种条件:

        (1)屏幕文件关闭:当程序结束和调用 fclose(stdout) 时,屏幕文件都会关闭,缓冲区的内容都会输出到屏幕上;

        (2)缓冲区满:当缓冲区被占满时,里面的内容会被输出到屏幕上,以腾出空间;

        (3)出现'\n':当缓冲区的内容中有换行符时,会把换行符之前(包括换行符)的所有数据输出到屏幕。

        3-2.scanf 与 fscanf

        与上面类似,scanf 是 fscanf 的一种特殊情况:scanf 是从键盘文件获取数据写到任意变量中;fscanf 是从任意文件(包括键盘文件)获取数据写到任意变量中。而指向键盘文件的文件指针是 stdin

        我们也就可以使用 stdin + fscanf 从键盘获取数据写到变量中:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 0;fscanf(stdin,"%d",&a); // 等价于 scanf("%d", &a);printf("a=%d\n", a);return 0;
}

 

下期预告:

        下一次的内容是综合案例,我们要使用前面所学的知识(特别是文件操作)写一个通讯录,它具有以下功能:

        (1)添加联系人

        (2)删除联系人

        (3)根据名字查询联系人的其他信息

        (5)修改已有联系人的信息

        (6)程序每次运行都保存上次的所有联系人

传送门:C语言-综合案例:通讯录

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 桂林自闭症寄宿学校:用关爱点亮未来
  • 神经网络的可解释性理论及工具
  • python如何获取html中的所有链接
  • 【Go】Go语言基本语法--注释、变量、常量
  • 算法设计与分析(整数划分问题
  • 哪些录屏工具最适合游戏录制?2024年Top4录屏工具梳理
  • Git学习尚硅谷(007 idea集成码云gitee)
  • Linux进程概念
  • vue 使用jszip,file-saver下载压缩包,自定义文件夹名,文件名打包下载为zip压缩包文件,全局封装公共方法使用。
  • Wni11 下 WSL 安装 CentOS
  • vue+el-table 可输入表格使用上下键进行input框切换
  • 【C语言】结构体超详细全讲解 (代码+万字文字+画图讲解)
  • Linux 大文件和大量小文件的复制策略
  • 常见SQL整理
  • SprinBoot+Vue药房管理系统的设计与实现
  • 「面试题」如何实现一个圣杯布局?
  • 【node学习】协程
  • 【刷算法】求1+2+3+...+n
  • 03Go 类型总结
  • Date型的使用
  • docker-consul
  • express + mock 让前后台并行开发
  • Golang-长连接-状态推送
  • Javascript Math对象和Date对象常用方法详解
  • JavaScript设计模式与开发实践系列之策略模式
  • mysql innodb 索引使用指南
  • 排序(1):冒泡排序
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • # 学号 2017-2018-20172309 《程序设计与数据结构》实验三报告
  • #1015 : KMP算法
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (C语言)共用体union的用法举例
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (接上一篇)前端弄一个变量实现点击次数在前端页面实时更新
  • (六)vue-router+UI组件库
  • (十六)串口UART
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)程序员技术练级攻略
  • (转)可以带来幸福的一本书
  • (状压dp)uva 10817 Headmaster's Headache
  • .chm格式文件如何阅读
  • .NET Core WebAPI中封装Swagger配置
  • .NET6 命令行启动及发布单个Exe文件
  • .net反混淆脱壳工具de4dot的使用
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • .NET框架设计—常被忽视的C#设计技巧
  • .NET下ASPX编程的几个小问题
  • /proc/interrupts 和 /proc/stat 查看中断的情况
  • ::before和::after 常见的用法
  • @converter 只能用mysql吗_python-MySQLConverter对象没有mysql-connector属性’...
  • @NotNull、@NotEmpty 和 @NotBlank 区别