《C语言程序设计进阶》7.1.3二进制文件

其实所有的文件最终都是二进制的
文本文件无非是用最简单的方式可以读写的文件

  • moretail
  • cat
  • vi

而二进制文件是需要专门的程序来读写的文件
文本文件的输入输出是格式化,可能经过转码

文本 vs 二进制

  • Unix喜欢用文本文件来做数据存储和程序配置
    • 交互式终端的出现使得人们喜欢用文本和计算机“talk”
    • Unix的shell提供了一些读写文本的小程序
  • Windows喜欢用二进制文件
    • DOS是草根文化,并不继承和熟悉Unix文化
    • PC刚开始的时候能力有限,DOS的能力更有限,二进制更接近底层

孰优孰劣

  • 文本
    • 优势是方便人类读写,而且跨平台
    • 缺点是程序输入输出要经过格式化,开销大
  • 二进制
    • 优点是程序读写快
    • 缺点是人类读写困难,而且不跨平台
      • int的大小不一致,大小端的问题…

程序为什么要文件

  • 配置
    • Unix用文本,Windows用注册表
  • 数据
    • 稍微有点量的数据都放数据库了
  • 媒体
    • 这个只能是二进制的
  • 现实是,程序通过第三方库来读写文件,很少直接读写二进制文件了

二进制读写

1
2
3
4
5
// 第一个参数为指针,表示要读或写的那块内存
// 第二个参数表示这块内存多大
// 第三个参数表示有几个这样的内存
size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);

注意FILE指针是最后一个参数
返回的是成功读写的字节数

为什么nitem

因为二进制文件的读写一般都是通过对一个结构变量的操作来进行的
于是nitem就是用来说明这次读写几个结构变量

测试一下

student.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _STUDENT_H_
#define _STUDENT_H_

#define STR_LEN 20

typedef struct _student {
char name[STR_LEN];
int gender;
int age;
}Student;

#endif

student.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include "student.h"

void getList(Student aStu[], int number);
int save(Student aStu[], int number);

int main(int atgc, char const* atgv[])
{
int number = 0;
printf("输入学生数量:");
scanf("%d", &number);
Student aStu[number];

getList(aStu, number);
if(save(aStu, number)){
printf("保存成功\n");
}else{
printf("保存失败\n");
}

return 0;
}

void getList(Student aStu[], int number)
{
char format[STR_LEN];
sprintf(format, "%%%ds", STR_LEN - 1); // "%19s"

int i;
for(i = 0; i < number; i++){
printf("第%d个学生:\n", i);
printf("\t姓名:");
scanf(format, aStu[i].name);
printf("\t性别(0-男,1-女,2-其他);");
scanf("%d", &aStu[i].gender);
printf("\t年龄:");
scanf("%d", &aStu[i].age);
}
}

int save(Student aStu[], int number)
{
int ret = -1;
FILE *fp = fopen("student.data", "w");
if(fp){
ret = fwrite(aStu, sizeof(Student), number, fp);
fclose(fp);
}
return ret == number; // 是==
}

在文件中定位

1
2
long ftell(FILE *stream);  
int fseek(FILE *stream, long offset, int whence);

whence
SEEK_SET:从头开始
SEEK_CUR:从当前位置开始
SEEK_END:从尾开始(倒过来)

测试一下(接前面测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include "student.h"

void read(FILE *fp, int index);

int main(int argc, char const *argv[])
{
FILE *fp = fopen("student.data", "r"); // 打开读
if(fp){
fseek(fp, 0L, SEEK_END); // fp移到文件最后,从后往前的0L
long size = ftell(fp); // 文件大小
int number = size / sizeof(Student); // 有几个这样的结构
int index = 0;
printf("有%d个数据,你要第几个:", number);
scanf("%d", &index);
read(fp, index - 1);
fclose(fp);
}

return 0;
}

void read(FILE *fp, int index)
{
fseek(fp, index * sizeof(Student), SEEK_SET); // 从头开始的index * sizeof(Student)处
Student stu;
if(fread(&stu, sizeof(Student), 1, fp) == 1){
printf("第%d个学生:", index + 1);
printf("\t姓名:%s\n", stu.name);
printf("\t性别:");
switch(stu.gender){
case 0: printf("男\n"); break;
case 1: printf("女\n"); break;
case 2: printf("其他\n"); break;
}
printf("\t年龄:%d\n", stu.age);
}
}

用软件UltraEdit可打开二进制文件student.data查看内容

可移植性

  • 这样的二进制文件不具有可移植性
    • int为32位的机器上写成的数据文件无法直接int为64位的机器上正确读出
  • 解决方案之一是放弃使用int,而是typedef具有明确大小的类型
  • 更好的方案是用文本
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2021 zhangguoliu
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信