在C++开发中,将数组保存到二进制文件是一项基础但容易出错的磁盘操作。与文本文件不同,二进制文件直接记录数据在内存中的字节序列,没有编码转换和格式分隔,因此读写效率更高,也更节省空间。实现这一功能的核心是利用文件流的二进制模式与write成员函数,把数组首地址开始的连续内存块整体写入。

一、使用ofstream以二进制模式写入数组
C++标准库中的std::ofstream用于输出文件流。要在二进制模式下写文件,必须在打开时传入std::ios::binary标志,否则在Windows等系统上换行符会被转换,破坏数据的原始字节。打开成功后,调用write方法,它接收两个参数:指向待写内存的char*指针和要写入的字节数。
对于基础类型数组,例如int arr[5],可以用sizeof(arr)得到整个数组字节长度,并把数组名转换为char*传入。由于数组名在多数表达式里会退化为指针,这种写法能正确写出全部元素。下面是一段完整示例,展示如何将整型数组写入二进制文件:
#include <fstream>
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int len = sizeof(arr) / sizeof(arr[0]);
std::ofstream outFile;
// 以二进制写模式打开文件
outFile.open("array.bin", std::ios::binary);
if (!outFile.is_open()) {
std::cerr << "无法打开文件" << std::endl;
return 1;
}
// 将数组整体写入,注意转换为char*并指定总字节数
outFile.write(reinterpret_cast<char*>(arr), sizeof(arr));
outFile.close();
std::cout << "数组已写入,元素个数:" << len << std::endl;
return 0;
}
上面的代码在运行后会在当前目录生成array.bin,文件大小恰好是5 * sizeof(int)即20字节(假设int为4字节)。这种方式不会在数字之间插入空格或换行,完全保留内存映像。
需要注意的是,write的第一个参数类型是const char*,因此必须用reinterpret_cast做强制转换,而不能用static_cast。此外,如果传入的是指向动态分配数组的指针(如int* p = new int[5]),则不能用sizeof(p)获取长度,因为此时p只是指针,应使用5 * sizeof(int)明确计算。
二、写入结构体数组与对齐问题
除了基础类型,很多时候我们需要把结构体数组写入二进制文件。C++结构体在内存中可能存在字节对齐填充,例如下面的结构体实际占用可能大于成员大小之和。直接write整个数组会把填充字节也一并写出,读取时用相同结构体定义读回就不会有问题,但跨平台或跨编译器时对齐规则不同会导致解析错误。
为了安全,可以用#pragma pack或alignas控制对齐,或者只序列化需要的字段。以下示例展示结构体数组的写入:
#include <fstream>
#include <cstring>
struct Point {
int x;
int y;
};
int main() {
Point pts[3] = {{1, 2}, {3, 4}, {5, 6}};
std::ofstream f("points.bin", std::ios::binary);
f.write(reinterpret_cast<char*>(pts), sizeof(pts));
f.close();
return 0;
}
这里sizeof(pts)是3 * sizeof(Point),由于Point是两个int,通常占用8字节,总文件为24字节。如果结构体里包含std::string等带有内部指针的类型,二进制拷贝只会保存指针值而非字符串内容,读回后变成悬空指针,因此只能用于纯POD(平凡旧数据类型)数组。
当需要确保文件格式稳定时,推荐手动逐个字段写入,或采用序列化库。但就单纯的数组转储需求而言,POD结构体配合二进制write是最简单直接的方案,只要保证读写两端编译环境一致即可。
三、从二进制文件读回数组
写出的数据必须能正确读回才有意义。读取时使用std::ifstream并以std::ios::binary打开,调用read方法,参数与write类似:目标缓冲区指针和字节数。缓冲区可以是栈上数组,也可以是堆上分配的内存。
下面的代码演示如何将前面写的array.bin读回并打印:
#include <fstream>
#include <iostream>
int main() {
int arr[5];
std::ifstream inFile("array.bin", std::ios::binary);
if (!inFile.is_open()) {
std::cerr << "打开失败" << std::endl;
return 1;
}
inFile.read(reinterpret_cast<char*>(arr), sizeof(arr));
inFile.close();
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
读取时务必保证缓冲区大小不小于文件中的数据结构尺寸。如果文件来自其他程序,最好先写入元素个数等元信息,读的时候先取长度再分配空间。例如先写int count,再写count个元素,这样能避免缓冲区溢出。
另外,二进制文件不具备自描述性,人眼无法直接阅读,调试时可以用十六进制编辑器查看。若出现数据错乱,优先检查打开时是否漏掉binary标志,以及读写两端的类型大小是否一致(如32位与64位下某些类型长度不同)。
四、常见错误与注意事项
初学者常犯的错误之一是用流插入运算符<<写数组,例如outFile << arr[i]循环写,这实际生成的是文本,而非二进制。另一个错误是把指针当数组写:int* p = arr; outFile.write((char*)p, sizeof(p));这里只写了指针本身的8字节,而不是数组内容。
还有一点是关于字节序。x86架构均为小端序,如果文件要在不同端序机器间交换,写之前应做主机序到网络序的转换,或者统一使用文本或标准化序列化格式。对于仅在本机暂存数据的场景,直接write无需处理端序。
总结来说,C++将数组写入二进制文件只需三步:以ios::binary打开ofstream、用reinterpret_cast转成char*、调用write并传入准确字节数。只要数组是纯值类型或POD结构体,该方法高效且可靠,是底层数据落盘的首选手段。