二进制输入输出 (binary I/O)#
这是一种序列化方式. 所谓序列化就是将对象转换为一种特定的格式 (序列化), 从而可以传输或存储, 而又能从这种特定格式转换为程序中的对象 (反序列化).
一般只是简单考一下, 记住怎么做即可.
二进制输出#
ofile.write(char const* string, streamsize count)
将
[string, string + count)
字符数组输出到ofile
中, 所以我们要将对象强行转换为字符数组.
1#include <fstream>
2
3int value = 0;
4
5// ofstream 会自动为打开方式加上 std::ios_base::out, 因此直接写 std::ios_base::binary 就行了
6std::ofstream ofile("输出文件路径", std::ios_base::binary);
7ofile.write(reinterpret_cast<char const*>(&value), sizeof(value));
二进制输入#
ifile.read(char* string, streamsize count)
从
ifile
读入字符到[string, string + count)
字符数组中, 所以我们要将对象强行转换为字符数组.
1#include <fstream>
2
3int value = 0;
4
5// ifstream 会自动为打开方式加上 std::ios_base::in, 因此直接写 std::ios_base::binary 就行了
6std::ifstream ifile("输入文件路径", std::ios_base::binary);
7ifile.read(reinterpret_cast<char*>(&value), sizeof(value));
std::string
二进制输出到文件后不能正常读取?#
现在, 假设我们要编写 Person
类, 它会有一个名字数据成员和一个年龄数据成员. 名字的长度很难确定, 所以我们用 std::string
作为名字的类型:
1struct Person {
2 public:
3 std::string name;
4 int age;
5};
而题目要求我们以二进制形式将 Person
输出到文件, 再从文件中读取它:
1void serialize(std::ostream& ostream, Person const& person) {
2 ostream.write(reinterpret_cast<char const*>(&person), sizeof(person));
3}
4
5void deserialize(std::istream& istream, Person& person) {
6 istream.read(reinterpret_cast<char*>(&person), sizeof(person));
7}
然而, 当我们使用这个序列化函数将 Person
输出到文件, 关闭程序, 再用反序列化函数从文件中读取 Person
时, 发现读取的内容并不正常, 甚至在之后使用时可能直接报错了!
这是因为与 int
等类型不同, std::string
除了栈上的指针还有堆上的字符数组, 而这样的 ostream.write
仅仅输出了 std::string
成员的指针部分.
栈? 堆? 如果还不了解它们也没关系, 让我们把这个问题转换为更熟悉的情况:
1struct Widget {
2 public:
3 int* pointer;
4};
5
6int main() {
7 int value = 0;
8
9 Widget widget;
10 widget.pointer = &value; // pointer 指向 value
11
12 std::ofstream ofile("输出文件路径", std::ios_base::binary);
13 ofile.write(reinterpret_cast<char const*>(&widget), sizeof(widget));
14};
对 widget
进行二进制输出, 会连同 value
一起输出出去吗? 不是的, Widget
类只有 pointer
是数据成员; value
虽然被 pointer
指向, 但不是 Widget
类的数据成员.
这样有远程 (remote) 部分 2这个说法出自《Elements of Programming》 的类都不能直接进行二进制输入输出. 如果实在需要二进制输入输出它们, 你需要根据其数据的逻辑关系 (类的不变式) 为它定义如何二进制输入输出.
提示
如果只是为了做作业, 请使用 std::array<char, 100>
或 char array[100]
这种没有远程部分的数组解决.
这种写法只有考试喜欢. 项目如果需要序列化, 会选择用 序列化库 将数据序列化为更高效的结构, 而不是折腾这样直接的二进制输入输出.
例如, 对于 std::string
, 你可以输出其长度和实际内容:
1void serialize(std::ostream& ostream, std::string const& string) {
2 // 长度
3 int size = string.size();
4 ostream.write(reinterpret_cast<char const*>(&size), sizeof(size));
5
6 // 实际内容 ↓ 单个元素的大小 * 元素数量
7 ostream.write(string.c_str(), sizeof(char) * size);
8}
9
10void deserialize(std::istream& istream, std::string& string) {
11 // 长度
12 int size = 0;
13 istream.read(reinterpret_cast<char*>(&size), sizeof(size));
14
15 // 根据读取字符串的长度给当前的 string 设置好长度
16 string.resize(size);
17
18 // 实际内容
19 istream.read(string.data(), sizeof(char) * size);
20}
21
22void serialize(std::ostream& ostream, Person const& person) {
23 serialize(ostream, person.name);
24 ostream.write(reinterpret_cast<char const*>(&person.age), sizeof(person.age));
25}
26
27void deserialize(std::istream& istream, Person& person) {
28 deserialize(istream, person.name);
29 istream.read(reinterpret_cast<char*>(&person.age), sizeof(person.age));
30}