百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

用C语言随机读写二进制文件(用c语言随机读写二进制文件的方法)

toyiye 2024-08-30 02:43 7 浏览 0 评论

本节主要讨论如何使用C语言随机读写二进制文件。

知识产权协议

允许以教育/培训为目的向学生或受众进行免费引用,展示或者讲述,无须取得作者同意。

不允许以电子/纸质出版为目的进行摘抄或改编。

文件既可以顺序读写,也可以随机读写。所谓顺序读写,可以简单理解为从头读/写到尾,数据项是一个接着一个进行读取/写入的;而随机读写则允许我们随时改变文件的当前读写位置,如果在非文件尾的位置写入内容,则相应位置的原始数据会被覆盖。

二进制文件可以视为字节流,程序为每一个被打开的二进制文件维护了一个读写位置标记,该标记为一个整数,表示当前读写位置相对于文件起始处的偏移量,以字节为单位。随着文件读写操作的进行,该读写位置会自动后移,其偏移量等于读写操作的字节数。

表20-6 文件随机访问函数(C语言)

函数

说明

rewind

void rewind(FILE* f);
说明:将文件f的读写位置移回文件头(起始处)。

fseek

int fseek(FILE* f, long offset, int origin);
说明:
??该函数通常应用于二进制文件,其将文件f的读写位置移动至偏离参考点(origin)指定偏移量(offset)的位置。参考点origin应为SEEK_SET、SEEK_CUR、SEEK_END之一,依次为文件头、当前读写位置和文件尾。
??函数应用于文本文件时,offset只能是0或者是前次ftell()的返回值,而origin只能是SEEK_SET。
??操作成功,函数返回0,否则返回非零值。

ftell

long ftell(FILE* f);
说明:
??对于二进制文件,返回文件的当前读写位置,即当前读写位置相对于文件头的偏移字节数。
??对于文本文件,函数返回值可能没有实际意义,但仍然可以配合fseek()函数恢复文件的读写位置。
??如果函数执行出错,返回-1。

fgetpos

int fgetpos(FILE* f, fpos_t* pos);
说明:
??获取文件f的当前读写位置,写入指针pos所指向的fpos_t对象中。指针pos指向的对象必须是已分配好的。通常情况下,fpos_t事实上是长整型或者长长整型。
??操作成功,函数返回0,否则返回非零值。

fsetpos

int fsetpos(FILE* f, const fpos_t* pos);
说明:
??设置文件f的读写位置,指针pos所指向的fpos_t对象即为目标位置。
??操作成功,函数返回0,否则返回非零值。

表20-6列出了C语言中用于二进制文件随机访问的常用函数,以这些函数为工具,我们可以在文件中随意移动读写位置,方便地读写二进制文件。

C语言程序BinaryPriceList展示了一个灵活的二进制商品价格表存储结构,该结构以不重复的固定的商品编号为基础进行工作。

//Project - BinaryPriceList
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>

typedef struct {
    int iNo;            //商品编号,不重复
    char sName[20];     //名称
    float fPrice;       //价格
    int iQuantity;      //在库数量
} Commodity;

bool locateCommodity(FILE* f, int iNo){
    rewind(f);          //读写指针回到文件头
    int t;
    while (true){
        if (fread(&t,sizeof(int),1,f)!=1)
            return false;
        if (t==iNo){
            fseek(f,-sizeof(int),SEEK_CUR);
            return true;
        }
        else
            fseek(f,sizeof(int)+sizeof(float)+20,SEEK_CUR);
    }
}

void saveCommodity(FILE* f, const Commodity* c){
    if (!locateCommodity(f,c->iNo))
        fseek(f,0,SEEK_END);

    fwrite(&c->iNo,sizeof(int),1,f);
    fwrite(c->sName,20,1,f);
    fwrite(&c->fPrice,sizeof(float),1,f);
    fwrite(&c->iQuantity,sizeof(int),1,f);
}

bool loadCommodity(FILE* f, int iNo, Commodity* c){
    if (!locateCommodity(f,iNo))
        return false;

    fread(&c->iNo,sizeof(int),1,f);
    fread(c->sName,20,1,f);
    fread(&c->fPrice,sizeof(float),1,f);
    fread(&c->iQuantity,sizeof(int),1,f);
    return true;
}

int main() {
    char sPath[512];
    if (getcwd(sPath,512)!=NULL)              //获取并打印当前工作路径
        printf("cwd: %s\n",sPath);

    FILE* f = NULL;
    if (access("commodity.dat",F_OK)==0)     //判断文件是否存在
        f = fopen("commodity.dat","rb+");     //打开已有文件进行随机读写
    else
        f = fopen("commodity.dat","wb+");     //打开新文件进行随机读写

    Commodity c1 = {1,"Apple",5.2764123f,2000};
    saveCommodity(f,&c1);

    printf("ftell(f): %ld\n",ftell(f));      //输出文件当前读写位置

    Commodity c3 = {3,"Beef",65.741f,5000};
    saveCommodity(f,&c3);
    Commodity c5 = {5,"Cherry",117.4f,500};
    saveCommodity(f,&c5);
    strcpy(c3.sName,"Pork");
    saveCommodity(f,&c3);

    Commodity t;
    printf("%-6s%-20s%10s%10s\n","No","Name","Price","Quantity");
    printf("----------------------------------------------\n");
    for (int i=1;i<=5;i++){
        if (!loadCommodity(f,i,&t))
            printf("%-6d%-20s%10.2f%10d\n",i,"NA",0.0,0);
        else
            printf("%-6d%-20s%10.2f%10d\n",t.iNo,t.sName,t.fPrice,t.iQuantity);
    }

    fclose(f);
    return 0;
}

上述代码的执行结果为:

cwd: D:\C2Cpp\C20_FileIO\build-BinaryPriceList-Desktop_Qt_5_14_1_MinGW_64_bit-Debug
ftell(f): 32
No    Name                     Price  Quantity
----------------------------------------------
1     Apple                     5.28      2000
2     NA                        0.00         0
3     Pork                     65.74      5000
4     NA                        0.00         0
5     Cherry                  117.40       500

上述程序执行完成后,我们得到一个二进制文件commodity.dat,其尺寸为96字节。为了便于描述上述程序的随机读写过程及工作原理,我们画出了commodity.dat的内部结构,请见图20-7。请读者注意,图20-7只是一个示意图,其并不能“精细”表达commodity.dat的内部结构。

第7 ~ 12行:一个Commodity表示一条商品价格信息。简单计算可知,一个Commodity对象的内存尺寸为4 + 20 + 4 + 4 = 32字节。如图20-7所示,程序执行完成后,commodity.dat内部储存了编号为1、3、5的三条价格信息,分别对应3个Commodity对象,每个32字节,3个共96字节,地址范围为0 ~ 95。这里所称的地址,表示相对于文件起始处的偏移字节数。

第14 ~ 27行:locateCommodity()函数用于在已打开的二进制文件f中查找编号为iNo的商品价格信息。如果查找成功,函数将f的读写位置移至对应的商品价格信息的起始处并返回true,否则返回false。

第15行:rewind(f)将文件f的读写位置移至文件头,即地址0处。

第17 ~ 26行:通过while“死”循环对文件进行顺序查找。

第18 ~ 19行:从文件f读取商品编号至整数t,如果fread()函数的返回值不是1,说明已抵达文件尾,查找失败,返回false。

第20 ~ 23行:如果第18行读到的商品编号t等于iNo,说明找到了指定记录,使用fseek()函数将读写位置后退4个字节至该商品价格信息的起始处,然后返回true。此处的SEEK_CUR表示参考位置为当前位置,负的sizeof(int)表示回退4个字节。

第25行:如果第18行读到的商品编号t不等于iNo,说明匹配不成功,使用fseek()函数将当前读写位置前移28个字节至下一条商品价格信息的起始处,然后继续循环。

表20-7列出了locateCommodity(f,3)的执行过程,请读者结合图20-7进行分析。

表20-7 locateCommodity(f,3)的执行过程

序号

说明

代码行

1

rewind(f)将读写位置移至文件头,即地址0处。

15

2

在文件的当前位置读出商品编号,其值为1,fread()执行完后,读写位置前移至地址4。

18

3

1不等于3,说明匹配不成功,执行第25行,读写位置前移28至地址32,也就是下一条记录的起始位置。

20~25

4

循环继续,在文件的当前位置读出商品编号,其值为3,读写位置因读动作前移至36。

18

5

3等于3,匹配成功,执行第21行,读写位置后移4个字节至地址32,地址32正好是编号为3的商品价格记录在文件中的起始位置。

20~23

6

返回true,向函数的调用者报告定位成功的消息。

22

第29 ~ 37行:saveCommodity()函数用于将c所指向的商品价格对象存入文件f,如果对应编号的商品价格记录已存在,则覆盖更新原有信息。

第30 ~ 31行:使用locateCommodity()函数在文件f中定位指定商品编号的记录。如果locateCommodity()返回false,表示未找到,则将读写位置移至文件尾,准备在文件尾追加新记录。此处的SEEK_END表示参考位置为文件尾,0表示相对于文件尾偏移0字节。

第33 ~ 36行:向文件中依次写入商品编号、名称、价格以及库存数量。当读写位置位于文件尾时,这4行代码的执行结果相当于往文件尾附加了一条新记录。当读写位置位于locateCommodity()函数定位的原有记录的起始处,这4行代码的执行结果相当于覆盖更新了原有记录。

第39 ~ 48行:loadCommodity()函数从文件中查找并读取指定编号的商品价格信息至c指向的结构体。如果指定的编号不存在,返回false,否则返回true。

第40 ~ 41行:使用locateCommodity()定位指定编号的记录,如果没找到,返回false表示读取失败。

第43 ~ 46行:如果指定编号的记录在文件中存在,locateCommodity()函数执行后,文件的读写位置正好位于该记录的起始处。依次读入编号、名称、价格和库存数量。

第47行:返回true表示读取成功。

第55 ~ 59行:以二进制读写模式打开文件commodity.dat备用。程序第一次运行时,commodity.dat文件可能不存在,以wb+模式打开,该模式确保当文件不存在时,自动新建一个文件。程序第N次运行时,commodity.dat文件已存在,以rb+模式打开,该模式确保文件的原有内容不会被截断。

函数access()用于判断文件fname是否具体指定的访问权限,其原型如下。当文件具有指定的权限时,返回0,否则返回-1。

int access(const char* fname, int mode);

参数mode可以为F_OK、X_OK、W_OK和R_OK等值,依次表示文件是否存在、是否可执行、是否可写、是否可读。

第61 ~ 62行:存入编号为1的商品价格信息。在程序第1次运行时,第62行在文件中新增记录;在第N次运行时,第62行在文件中覆盖更新记录。

第64行:打印输出文件的当前读写位置。由于刚刚在第62行写入1号商品信息完毕,该读写位置的理论值应为32,因为如前所述,一行记录空间占用正好是32字节。

第66 ~ 69行:存储3号及5号商品价格信息。

第70 ~ 71行:修改3号商品的名称,再次将其写入文件。第71行执行前3号商品记录肯定已存在于文件中,因此第71行事实上覆盖更新了原有记录。执行结果的第7行证实,3号商品的名称被正确修改为Pork。

第73 ~ 81行:逐一读取并打印编号1至5的商品价格信息。执行结果的第6行和第8行证实,由于2号商品和4号商品不存在,第77行的loadCommodity()返回了false,第78行将对应的商品名称打印为“NA”,意为不可用(not available)。

第83行:关闭文件f。


本案例节选自作者编写的教材及配套实验指导书。

《C++编程基础及应用》(高等教育出版社,出版过程中)

《Python编程基础及应用》,高等教育出版社

《Python编程基础及应用实验教程》,高等教育出版社

高校教师同行如果期望索取样书,教学支持资料,加群,请私信作者,联系时请提供学校及个人姓名为盼,各高校在读学生勿扰为谢。

青少年读者们如果期望系统性地学习Python及C/C++程序设计语言,欢迎尝试下述今日头条(西瓜)免费视频课程。

C/C++从入门到放弃(重庆大学现场版)

Python编程基础及应用(重庆大学现场版)

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码