C语言有关文件的操作

打开文件与关闭文件

在编写代码时,我有一个习惯是“保证一一对应”。
写下代码fopen()之后,还没有写对文件进行增删查改等操作的代码,先立刻写上fclose(),避免忘记关闭FILE* fd的情况。

不关闭fd,在fopen()次数较少的情况下,系统不会出现错误,但是当打开的fd多了,会造成段错误。这类问题需要反复调用fopen()才会出现错误,如果测试次数不多的话容易忽略该问题,因此建议fopen()fclose()配套使用,避免此类问题。

fopen()打开文件后,还需要需要判断文件指针是否为NULL,这一步是必要的,否则接下来的fclose(NULL)会造成段错误。

文件的打开模式与随机访问

文件的随机访问依赖于两个函数:fseek()ftell()fseek()函数将文件看作数组,可以移动至任意字节处。

文件的打开方式限制了fseek()的功能。假设有文件test.bin,文件的原始内容如下,文件大小为5字节:
文件内容
设计如下程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{   
    // 打开文件
    // 文件原始内容为0x11 0x22 0x33 0x44 0x55,共5字节
    FILE* fd = fopen("/home/qnh/Desktop/film/test.bin", "ab");
    if(NULL == fd)
    {
        printf("FILE[%s] \n LINE[%d] \n FUN[%s]: Open file failed ! \n", __FILE__, __LINE__, __FUNCTION__);
        return 0;
    }

    // 读取文件内容并显示
    fseek(fd, 3, SEEK_SET);
    int offset = ftell(fd);
    printf("offset_after_fseek: %d \n", offset);
    int ch = getc(fd);
    printf("ch: %02x \n", ch);

    offset = ftell(fd);
    printf("offset_after_getc: %d \n", offset);

    // puts()方法向文件中写入数据
    putc(0xAA, fd);
    offset = ftell(fd);
    printf("offset_after_puts: %d \n", offset);

    // fwrite()方法向文件中写入数据
    fseek(fd, 3, SEEK_SET);
    unsigned char data[2] = {0xBB, 0xCC};
    fwrite(data, 1, 2, fd);
    offset = ftell(fd);
    printf("offset_after_fwrite: %d \n", offset);

    fclose(fd);
}

举例来说,以"ab"模式打开文件,并使用fseek()移动文件指针。先进行读操作,此时使用ftell()输出文件的当前位置,可以发现,文件指针是移动至offset的的,但是使用getc()函数,无法读取到文件在该偏移量的内容,读取到的内容是0xFF,这是合理的,因为"ab"模式是写模式,并没有读取文件内容的权限,如果使用"ab+"模式打开文件,就可以读取文件内容了。
程序输出

再测试一下写操作,可以发现,写入的0xAA以及0xBB、0xCC被追加到了文件的末尾,而不是offset处,并且此时ftell()显示文件指针移动到了文件的末尾。说明"a模式"下写入数据的情况,与fseek()函数移动的文件指针无关,数据只能追加到文件的末尾。见参考链接【1】。
写入内容的文件

文件结束符EOF

见参考链接【2】

EOF 是 End Of File 的缩写。在C语言中,它是在标准库中定义的一个宏,在数值上为int类型的-1(0xFFFFFFFF)。
在《C primer plus》的13.2.4 文件结尾章节,有如下描述:

如果getc()函数在读取一个字符时发现是文件结尾,它将返回一个特殊值EOF。所以C程序只有在读到超过文件末尾时才会发现文件的结尾。

这句话书里写的比较混乱,我第一次读产生了歧义。
首先,书中的文件结尾和文件末尾是两个不同的概念,我的理解是,文件末尾是文件的最后一个字节,而文件结尾是一个“哨兵”字符,指向文件末尾的后一个字节,也就是SEEK_END
其次,不要认为EOF是附加在文件结尾处的字符,实际上文件的结尾并不存在一个EOF字符,EOF表示一种状态。getc()函数读到超过文件末尾时,会发现文件已经结束了,此时getc()返回EOF。

书的13.5.1 fseek()和ftell()的工作原理一章,表13.3将SEEK_END和文件末尾混为一谈,与13.2.4 文件结尾超过文件末尾时才会发现文件的结尾产生了矛盾。
鉴于书中这几个章节的混乱,我建议抛弃文件末尾的概念,只留“文件结尾 = SEEK_END”这个概念,概念解释图如下:

概念图

getc()读完整个文件,此时满足(ch == EOF)时,文件指针指向了SEEK_END处,此时想要读取文件的最后一个字节,需要从SEEK_END处回退一个字节。

fseek(fd, -1, SEEK_END);

当文件指针指向SEEK_END时,ftell()表示文件开始处到SEEK_END的字节数,也可以认为是从文件开始处移动到SEEK_END所需要的次数,也可以认为是文件的大小。

获取文件内容

在工程中经常需要逐字节获取文件内容。

如果使用while()循环+判断EOF的方法,getchargetcfgets的返回值需要使用int类型变量接收,如果使用char类型变量接收,可能会导致错误。

int main()
{   
    FILE* fd = fopen("/home/qnh/Desktop/film/test.bin", "rb+");
    if(NULL == fd)
    {
        printf("FILE[%s] \n LINE[%d] \n FUN[%s]: Open file failed ! \n", __FILE__, __LINE__, __FUNCTION__);
        return 0;
    }

    int ch = getc(fd);
    while(EOF != ch)
    {
        printf("%02x ", ch);
        ch = getc(fd);
    }

    fclose(fd);
}

使用char类型变量可能产生错误的原因在于,假设getc()读取到的字节为0xFF,则getc()返回值为0x000000FF,赋值给char类型的ch后,ch的值为0xFF,那么(EOF == ch)条件将会成立,而此时文件尚未读到结尾,while()循环会提前退出。见参考链接【3】。

或者先获取文件的大小,再使用for()循环读取文件,这种方法就不需要使用EOF了。
获取文件大小后,循环的次数就确定了,可以使用for()循环获取文件内容,此时可以使用char类型变量接收。
需要注意文件指针的位置,我常用的做法是在使用ftell()前,先将文件指针移动至SEEK_END处,使用ftell()后,再将文件指针移动至SEEK_SET处,从头开始遍历文件。

int main()
{   
    FILE* fd = fopen("/home/qnh/Desktop/film/test.bin", "rb+");
    if(NULL == fd)
    {
        printf("FILE[%s] \n LINE[%d] \n FUN[%s]: Open file failed ! \n", __FILE__, __LINE__, __FUNCTION__);
        return 0;
    }

    fseek(fd, 0, SEEK_END);
    int size = ftell(fd); // 获取文件大小
    fseek(fd, 0, SEEK_SET);
    for(int i = 0; i < size; i++)
    {
        printf("%02x ", getc(fd));
    }

    fclose(fd);
}

参考连接

【1】https://blog.csdn.net/veghlreywg/article/details/103348856
【2】https://blog.csdn.net/chenaibo/article/details/6062773
【3】https://blog.csdn.net/fengyuruhui/article/details/1682495

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/63436.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎么在树莓派环境上搭建web网站,并发布到外网可访问,今天教给大家

怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f; 文章目录 怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f;概述使用 Raspberry Pi Imager 安装 Raspberry Pi OS测试 web 站点安装静态样例站点 将web站点发布到公网安装 Cpolarcpo…

【Redis】——AOF持久化

什么是AOF日志 AOF日志是redis为数据的持久化提供了的一个技术,日志里面记录着执行redis写命令。每当redis执行一条写命令的时候&#xff0c;就会将该命令记录 到AOF日志当中。当redis启动的时候&#xff0c;可以加载AOF日志中的所有指令&#xff0c;并执行这些指令恢复所有的…

[国产MCU]-BL602开发实例-定时器

定时器 文章目录 定时器1、BL602定时器介绍2、定时器驱动API介绍3、定时器使用实例3.1 单次计时3.2 持续计时通用定时器,用于定时,当时间到达我们所设置的定时时间会产生定时中断,可以用来完成定时任务。本文将详细介绍如何使用BL602的定时器功能。 1、BL602定时器介绍 BL6…

1、sparkStreaming概述

1、sparkStreaming概述 1.1 SparkStreaming是什么 它是一个可扩展&#xff0c;高吞吐具有容错性的流式计算框架 吞吐量&#xff1a;单位时间内成功传输数据的数量 之前我们接触的spark-core和spark-sql都是处理属于离线批处理任务&#xff0c;数据一般都是在固定位置上&…

解决vite+vue3项目npm装包失败

报错如下&#xff1a; Failed to remove some directories [ npm WARN cleanup [ npm WARN cleanup D:\\V3Work\\v3project\\node_modules\\vue, npm WARN cleanup [Error: EPERM: operation not permitted, rmdir D:\V3Work\v3project\node_modules\vue\reactivity\…

【腾讯云Cloud Studio实战训练营】使用React快速构建点餐H5

文章目录 前言一、Cloud Studio是什么二、Cloud Studio特点三、Cloud Studio使用1.访问官网2.账号注册3.模板选择4.模板初始化5.H5开发安装 antd-mobile安装 Less安装 normalize上传项目需要的素材替换App.js主文件项目启动、展示 6.发布仓库 总结 前言 随着云计算产业的发展&…

数据结构 | 搜索和排序——排序

目录 一、冒泡排序 二、选择排序 三、插入排序 四、希尔排序 五、归并排序 六、快速排序 排序是指将集合中的元素按照某种顺序排序的过程。 一、冒泡排序 冒泡排序多次遍历列表。它比较相邻的元素&#xff0c;将不合顺序的交换。每一轮遍历都将下一个最大值放到正确的位…

合规管理,企业生存之本!这4大方法,助你规避风险

当下,合规管理已成为企业必修的一门学问。无论是上市公司还是民营企业,都面临着日益严苛的监管合规要求。然而,许多企业在在应对频繁更新的合规要求时,仍然手忙脚乱,合规工作参差不齐。 专家分析认为,企业合规困境的主要症结在于,业务运转过程中产生了大量证明文件,但企业对其…

【N32L40X】学习笔记14-在RT-thread系统中读取eeprom数据

eeprom 说明 eeprom介绍 AT24C01A&#xff0c;1K串行EEPROM&#xff1a;内部组织16页8字节&#xff0c;1K需要一个7位数据字地址进行随机字寻址。AT24C02,2K串行EEPROM&#xff1a;内部组织32页8字节&#xff0c;2K需要一个8位数据字地址进行随机字寻址。AT24C04,4K串行EEPRO…

webpack基础知识九:如何提高webpack的构建速度?

一、背景 随着我们的项目涉及到页面越来越多&#xff0c;功能和业务代码也会随着越多&#xff0c;相应的 webpack 的构建时间也会越来越久 构建时间与我们日常开发效率密切相关&#xff0c;当我们本地开发启动 devServer 或者 build 的时候&#xff0c;如果时间过长&#xff…

Latex安装与环境配置(TeXlive、TeXstudio与VS code的安装)编译器+编辑器与学习应用

TeXlive 配置Tex排版系统需要安装编译器+编辑器。TeX 的源代码是后缀为 .tex 的纯文本文件。使用任意纯文本编辑器,都可以修改 .tex 文件:包括 Windows 自带的记事本程序,也包括专为 TeX 设计的编辑器(TeXworks, TeXmaker, TeXstudio, WinEdt 等),还包括一些通用的文本编…

vue diff 前后缀+最长递增子序列算法

文章目录 查找相同前后缀通过前后缀位置信息新增节点通过前后缀位置信息删除节点 中间部份 diff判断节点是否需要移动删除节点删除未查找到的节点删除多余节点 移动和新增节点最长递增子序列 求解最长递增子序列位置信息 查找相同前后缀 如上图所示&#xff0c;新旧 children 拥…

用Python获取链家二手房房源数据,做可视化图分析数据

前言 数据采集的步骤是固定: 发送请求, 模拟浏览器对于url地址发送请求获取数据, 获取网页数据内容 --> 请求那个链接地址, 返回服务器响应数据解析数据, 提取我们需要的数据内容保存数据, 保存本地文件 所需模块 win R 输入cmd 输入安装命令 pip install 模块名 (如果你…

Gpt微信小程序搭建的前后端流程 - 前端小程序部分-1.基础页面框架的静态设计(二)

Gpt微信小程序搭建的前后端流程 - 前端小程序部分-1.基础页面框架的静态设计(二) 在开始这个专栏&#xff0c;我们需要找一个小程序为参考&#xff0c;参考和仿照其界面&#xff0c;聊天交互模式。 这里参考小程序-小柠AI智能聊天&#xff0c;可自行先体验。 该小程序主要提供了…

梳理日常开发涉及的负载均衡

负载均衡是当前分布式微服务时代最能提及的词之一&#xff0c;出于对分层、解耦、弱依赖、可配置、可靠性等概念的解读&#xff0c;一对一的模式变得不再可信赖&#xff0c;千变万化的网络环境中&#xff0c;冗余和备份显得格外重要&#xff0c;稍大型的系统就会存在大量微服务…

瞄准产业应用,大模型加持的深兰科技AI虚拟数字人落地业务场景

伴随ChatGPT的问世&#xff0c;在技术与商业运作上都日渐发展成熟的AI数字人产业正持续升温。 目前的AI数字人不仅拥有超高“颜值”&#xff0c;同时还拥有更为丰富的、细腻的表情和动作。更有甚者&#xff0c;AI数字人已经具备自定义构建知识图谱、自主对话、不断学习成长的能…

Win7 专业版Windows time w32time服务电脑重启后老是已停止

环境&#xff1a; Win7 专业版 问题描述&#xff1a; Win7 专业版Windows time w32time服务电脑重启后老是已停止 解决方案&#xff1a; 1.检查启动Remote Procedure Call (RPC)、Remote Procedure Call (RPC) Locator&#xff0c;DCOM Server Process Launcher这三个服务是…

.Net6 Web Core API --- AOP -- log4net 封装 -- MySQL -- txt

目录 一、引入 NuGet 包 二、配置log4net.config 三、编写Log4net封装类 四、编写日志记录类 五、AOP -- 拦截器 -- 封装 六、案例编写 七、结果展示 一、引入 NuGet 包 log4net Microsoft.Extensions.Logging.Log4Net.AspNetCore MySql.Data ---- MySQL…

Embedding入门介绍以及为什么Embedding在大语言模型中很重要

Embeddings技术简介及其历史概要 在机器学习和自然语言处理中&#xff0c;embedding是指将高维度的数据&#xff08;例如文字、图片、音频&#xff09;映射到低维度空间的过程。embedding向量通常是一个由实数构成的向量&#xff0c;它将输入的数据表示成一个连续的数值空间中…

AcWing1171. 距离(lcatarjan)

输入样例1&#xff1a; 2 2 1 2 100 1 2 2 1输出样例1&#xff1a; 100 100输入样例2&#xff1a; 3 2 1 2 10 3 1 15 1 2 3 2输出样例2&#xff1a; 10 25 #include<bits/stdc.h> using namespace std; typedef long long ll; const int N2e55; int n,m,x,y,k,r…