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

C语言进阶教程:文件操作高级 - 二进制文件的读写

yund56 2025-05-28 23:43 9 浏览

1. 什么是二进制文件?

与文本文件不同,二进制文件存储的是原始的字节数据,而不是可读的字符。这意味着二进制文件可以存储任何类型的数据,如图像、音频、视频、程序的可执行文件、或者自定义的结构体数据等。它们不依赖于特定的字符编码(如ASCII或UTF-8),而是直接反映数据在内存中的表示。

与文本文件的区别:

  • 文本文件:以字符为单位进行读写,内容是人类可读的文本。在不同操作系统上,行尾结束符可能不同(例如,Windows使用CRLF \r\n,Linux/macOS使用LF \n)。C语言在读写文本文件时,可能会对这些行尾符进行转换。
  • 二进制文件:以字节为单位进行读写,内容是原始的二进制数据流。读写时不会进行任何转换,写入什么字节,读出来的就是什么字节。

2. 打开二进制文件

在C语言中,使用 fopen() 函数打开文件。为了以二进制模式打开文件,需要在模式字符串中包含 "b" 字符。

  • "rb": 以二进制只读方式打开文件。文件必须存在。
  • "wb": 以二进制只写方式打开文件。如果文件存在,则清空文件内容;如果文件不存在,则创建新文件。
  • "ab": 以二进制追加方式打开文件。如果文件不存在,则创建新文件。写入的数据会添加到文件末尾。
  • "rb+": 以二进制读写方式打开文件。文件必须存在。
  • "wb+": 以二进制读写方式打开文件。如果文件存在,则清空文件内容;如果文件不存在,则创建新文件。
  • "ab+": 以二进制读写追加方式打开文件。如果文件不存在,则创建新文件。读取从文件头开始,写入从文件尾开始。
 #include <stdio.h>
 
 int main() {
     FILE *fp_read, *fp_write;
 
     // 以二进制只读方式打开
     fp_read = fopen("data.bin", "rb");
     if (fp_read == NULL) {
         perror("Error opening data.bin for reading");
         return 1;
     }
     printf("File data.bin opened for binary reading.\n");
 
     // 以二进制只写方式打开
     fp_write = fopen("output.bin", "wb");
     if (fp_write == NULL) {
         perror("Error opening output.bin for writing");
         fclose(fp_read); // 关闭已打开的文件
         return 1;
     }
     printf("File output.bin opened for binary writing.\n");
 
     // ... 文件操作 ...
 
     fclose(fp_read);
     fclose(fp_write);
     printf("Files closed.\n");
 
     return 0;
 }

3. 读写二进制数据

对于二进制文件,通常使用 fread()fwrite() 函数进行读写操作。这两个函数直接操作字节块,非常适合处理结构体、数组等数据。

fwrite()函数

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

  • ptr: 指向要写入数据的内存块的指针。
  • size: 要写入的每个数据项的大小(以字节为单位)。
  • nmemb: 要写入的数据项的数量。
  • stream: 指向 FILE 对象的指针,该对象指定了输出流。

函数返回成功写入的数据项的总数。如果发生错误,返回值会小于 nmemb

fread()函数

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

  • ptr: 指向用于存储读取数据的内存块的指针。
  • size: 要读取的每个数据项的大小(以字节为单位)。
  • nmemb: 要读取的数据项的数量。
  • stream: 指向 FILE 对象的指针,该对象指定了输入流。

函数返回成功读取的数据项的总数。如果到达文件末尾或发生错误,返回值会小于 nmemb。可以使用 feof()ferror() 来区分这两种情况。

示例:读写结构体

 #include <stdio.h>
 #include <stdlib.h>
 
 // 定义一个结构体
 typedef struct {
     int id;
     char name[50];
     double score;
 } Student;
 
 int main() {
     FILE *outfile, *infile;
     Student student_out = {1, "Alice", 95.5};
     Student student_in;
 
     // 写入结构体到二进制文件
     outfile = fopen("students.dat", "wb");
     if (outfile == NULL) {
         perror("Error opening students.dat for writing");
         return 1;
     }
 
     size_t written_count = fwrite(&student_out, sizeof(Student), 1, outfile);
     if (written_count < 1) {
         perror("Error writing to students.dat");
         fclose(outfile);
         return 1;
     }
     printf("Student data written to students.dat\n");
     fclose(outfile);
 
     // 从二进制文件读取结构体
     infile = fopen("students.dat", "rb");
     if (infile == NULL) {
         perror("Error opening students.dat for reading");
         return 1;
     }
 
     size_t read_count = fread(&student_in, sizeof(Student), 1, infile);
     if (read_count < 1) {
         if (feof(infile)) {
             printf("End of file reached before reading student data.\n");
         } else if (ferror(infile)) {
             perror("Error reading from students.dat");
         }
         fclose(infile);
         return 1;
     }
 
     printf("\nStudent data read from students.dat:\n");
     printf("ID: %d\n", student_in.id);
     printf("Name: %s\n", student_in.name);
     printf("Score: %.2f\n", student_in.score);
 
     fclose(infile);
 
     return 0;
 }

示例:读写数组

 #include <stdio.h>
 
 #define ARRAY_SIZE 5
 
 int main() {
     FILE *fp;
     int numbers_out[ARRAY_SIZE] = {10, 20, 30, 40, 50};
     int numbers_in[ARRAY_SIZE];
     size_t count;
 
     // 写入整数数组到二进制文件
     fp = fopen("numbers.bin", "wb");
     if (fp == NULL) {
         perror("Error opening numbers.bin for writing");
         return 1;
     }
     count = fwrite(numbers_out, sizeof(int), ARRAY_SIZE, fp);
     if (count < ARRAY_SIZE) {
         perror("Error writing numbers to numbers.bin");
     }
     printf("%zu integers written to numbers.bin\n", count);
     fclose(fp);
 
     // 从二进制文件读取整数数组
     fp = fopen("numbers.bin", "rb");
     if (fp == NULL) {
         perror("Error opening numbers.bin for reading");
         return 1;
     }
     count = fread(numbers_in, sizeof(int), ARRAY_SIZE, fp);
     if (count < ARRAY_SIZE) {
         if (feof(fp)) {
             printf("Reached EOF before reading all numbers.\n");
         } else if (ferror(fp)) {
             perror("Error reading numbers from numbers.bin");
         }
     }
     printf("%zu integers read from numbers.bin\n", count);
     fclose(fp);
 
     printf("Numbers read: ");
     for (int i = 0; i < count; i++) {
         printf("%d ", numbers_in[i]);
     }
     printf("\n");
 
     return 0;
 }

4. 注意事项

  1. 字节序(Endianness): 当在不同体系结构(大端 vs. 小端)的机器之间交换二进制文件时,需要注意字节序问题。fwritefread 不会自动处理字节序转换。如果需要跨平台兼容性,可能需要手动进行字节序转换,或者使用标准化的数据格式(如 Protocol Buffers, JSON (文本), XML (文本))。
  2. 数据对齐(Data Alignment): 结构体在内存中的存储可能会因为数据对齐而包含填充字节。当直接将结构体写入文件并在不同编译器或平台上读取时,如果对齐方式不同,可能会导致问题。可以使用 #pragma pack (或编译器特定指令) 来控制结构体打包,但这会牺牲一些性能。
  3. 指针: 不能直接将包含指针的结构体写入二进制文件并期望在另一个程序或同一程序的不同运行实例中正确读取。指针存储的是内存地址,这些地址在不同的上下文是无效的。如果需要存储链式结构,需要将其序列化(例如,将指针转换为相对偏移量或ID,并在读取时重建链接)。
  4. 文件大小: 二进制文件可以非常大,确保有足够的磁盘空间,并在读写时进行错误检查。
  5. 错误处理: 始终检查 fopen(), fread(), fwrite() 等函数的返回值,并使用 ferror()feof() 来确定错误原因。

总结

二进制文件的读写是C语言文件操作中的重要部分,它允许程序高效地存储和检索非文本数据。通过 fopen() 以二进制模式打开文件,并使用 fread()fwrite() 进行数据块的读写,可以方便地处理各种复杂的数据结构。然而,在进行二进制I/O时,务必注意字节序、数据对齐和指针等潜在问题,以确保数据的正确性和可移植性。

相关推荐

没有获得Windows 10 20H2升级通知,怎样直接升级

微软公司已经正式发布Windows1020H2操作系统,在正常情况下,微软只会首先推送到少量电脑,然后一边推送一边采集遥测数据。收集遥测数据可以确定哪些电脑可以更新,哪些电脑在更新后可能会失败,微...

不想让人随便卸载你安装的程序,用这四招,他将无计可施

Windows10不提供设置删除应用程序限制的功能,有几种间接方法可以防止用户删除操作系统中的程序和游戏。一、WindowsInstaller服务使用Windows工具,可以部分限制用户的权限。如...

一文看懂苹果全球开发者大会 五大系统全面升级

来源:环球网【环球网智能报道记者张阳】北京时间6月23日凌晨1点,苹果全球开发者大会(WWDC2020)如期举行,还是那个熟悉的乔布斯剧院,依旧是高水准的视频展示,但是这届WWDC,却是苹果历史...

无需等待微软分批推送,23H2可借助注册表快速获取Win11 24H2更新

IT之家10月15日消息,Windows1124H2正在分批推送,但由于存在多种Bug,微软已经开始放缓其推送节奏。WindowsLatest发现,Windows1123H2...

办公小技巧:剑走偏锋 PPT中打造动态图表

年底到了少不了又要制作各种总结报表,为了让自己的报表与众不同,我们可以借助PowerPoint动画组件+报表的方式,打造出更为出彩的动态图表。下面以PowerPoint2016为例,介绍如何使用三维...

文档表格 版本差异何在

在办公过程中,对文档或表格的修改是司空见惯的事。那么,一份文档做了内容改动,如何知道差异在哪里?一份表格改动部分数据,如何知道哪些有所变动?不要说审阅和修订功能,因为不是所有人都会用这些功能来标注的,...

Excel VBA自制日历组件16色可选 完美替代VBA日期控件

本日期组件可跟随单元格跟随窗体中ActiveX文本框组合框控件16种配色可选私信回复880日历可体验效果使用说明1打开自己需要应用日历面板的Excel表,注意必须是启用VBA的格式2在...

如何从交互角度读懂产品需求文档

作为设计师,理解产品经理提供的需求文档是交互设计工作的重要前提与起点,然而对于很多设计师来说,需求文档内容通常非常复杂,设计师们需要花费大量时间去消化、理解和归纳。本文作者结合公司示例,分析设计师如何...

植入让文档变得更强大

有效地利用文档置入技术,会让我们的常用文档功能变得更加强大,实现更加高效或有趣的应用。1.写字板文档嵌入其他文档有时,我们要组织一个大型的文档,但是这些文档的内容可能来自于不同种类的文档编辑器,比如...

Office 2016滚动文本框 顺手就来

【电脑报在线】如果一页PPT内容较多无法在完全显示,就需要用到滚动文本框,在PPT2016中借助控件即可快速制作滚动文本框。在“告诉我你想要做什么”输入“文本框控件”,在搜索结果点击“文本框(Acti...

Axure的多状态复选树

本文将详细介绍如何在Axure中实现一种增强型的多状态复选树组件,它不仅支持全选、半选和未选等状态,还具备动态加载、关键字筛选等高级功能。多状态复选树(Multi-StateCheckboxTre...

办公小技巧:PPT中控件图表巧联动

在利用PPT进行图表演示时,操作者有可能要与图表进行交互联动,比如通过输入数据来预测产品的生产情况等,这时就需要用到“开发工具”中的控件了。几个控件配合几句VBA代码,就可以轻松实现上述交互联动效果(...

用好插件——找回火狐的旧功能

现在的软件,特别是浏览器类软件,更新换代速度都很快,而且无论是外观界面还是系统组件都会有较大的变化,这样会让很多朋友无所适从。以大家常用的火狐浏览器为例,它就已经升级到了最新的35版,而且在新版中对很...

重新认识控件(二)

图片和文字,都是一种数据形式。我平时对文本框的录入,报错和提交的设计比较多。最近涉及到图片控件的设计,细细琢磨一下,这玩意还有一些平时没太注意的细节点,感觉对于其他控件的设计有指导意义,特此总结一下传...

JSA宏教程——在文档中添加复合框控件

上一期,我们初步认识了控件Control,本节我们将继续控件的相关内容。这几期我们将逐一介绍相关控制。本节先介绍复合框(也叫组合框)Combobox。复合框的作用复合框就是一个下拉选项框,一次显示一个...