二进制文件读写技术求助:实现写入、定位读取与数据覆盖
问题与解决方案
原始代码
#include <iostream> #include <fstream> #include <cstring> struct Data { char *Hold1; char *Hold2; }; int main() { //Variables int choice , loop = 0, RecAmount = 0; std::string DepName, output1; double inp2, output2; ////File Check std::fstream inventory; inventory.open("inventory.dat", std::ios::out|std::ios::in|std::binary ); if (inventory.fail()){ inventory.open("inventory.dat", std::ios::out|std::ios::in | std::binary| std::ios::trunc); std::cout << "Error opening file...."; return 0; } while (loop < 1) { std::cout << " Read/write testing "; std::cout << "1. Input Data "; std::cout << "2. Read Data Output "; std::cin >> choice; ////Input 2 strings if (choice == 1) { std::cout << "Testing input "; struct Data test1; test1.Hold1 = "First test"; test1.Hold2 = 29; } if (choice == 2) { std::cout << "Testing output "; //// Add way to choose which data is read and displayed inventory.read ((char *)& inp2, sizeof(Data)); std::cout << "Character input = " << output1 << std::endl; } } }
需求
- 向名为
inventory.dat的二进制文件写入数字和字符数组 - 可定位并读取指定信息(如输入3组数据后仅显示第2组)
- 可覆盖任意指定数据(如找到第2组数据并修改)
解决方案
1. 修正结构体定义
原始结构体用char*指针存储数据,直接写入二进制文件只会保存内存地址,程序重启后地址失效。改成固定大小的字符数组+基础数字类型,确保二进制存储的一致性:
#include <iostream> #include <fstream> #include <cstring> // 定义POD类型结构体,适合二进制存储 struct Data { char Hold1[50]; // 预留足够空间存储字符串(最多49字符) int Hold2; // 用int存储数字,替代无效的char* };
2. 写入数据到二进制文件
使用fstream::write方法追加数据到文件末尾,每次写入一个完整的Data结构体:
// 写入数据函数 void writeData(std::fstream& file, const Data& data) { file.seekp(0, std::ios::end); // 移动写指针到文件末尾 file.write(reinterpret_cast<const char*>(&data), sizeof(Data)); }
在主菜单的写入数据分支调用:
if (choice == 1) { Data test1; std::cin.ignore(); // 清除输入缓冲区的换行符 std::cout << "输入字符串(最多49字符):"; std::cin.getline(test1.Hold1, 50); std::cout << "输入数字:"; std::cin >> test1.Hold2; writeData(inventory, test1); std::cout << "数据写入成功!\n"; }
3. 读取指定位置的数据
二进制文件中每个Data结构体占sizeof(Data)字节,第N条数据的起始位置为(N-1)*sizeof(Data)。用seekg定位读指针后读取:
// 读取指定序号的数据(序号从1开始) bool readData(std::fstream& file, int index, Data& outData) { // 计算文件总条目数 file.seekg(0, std::ios::end); std::streampos totalBytes = file.tellg(); int totalItems = totalBytes / sizeof(Data); if (index < 1 || index > totalItems) { std::cout << "无效的条目序号!\n"; return false; } // 定位到目标条目起始位置 std::streampos targetPos = (index - 1) * sizeof(Data); file.seekg(targetPos); file.read(reinterpret_cast<char*>(&outData), sizeof(Data)); return true; }
在主菜单的读取数据分支调用:
if (choice == 2) { int index; std::cout << "输入要读取的条目序号:"; std::cin >> index; Data result; if (readData(inventory, index, result)) { std::cout << "\n第" << index << "条数据:\n"; std::cout << "字符串:" << result.Hold1 << "\n"; std::cout << "数字:" << result.Hold2 << "\n"; } }
4. 修改指定位置的数据
修改逻辑与读取类似,定位到目标位置后用write覆盖原有数据:
// 修改指定序号的数据 bool updateData(std::fstream& file, int index, const Data& newData) { file.seekg(0, std::ios::end); std::streampos totalBytes = file.tellg(); int totalItems = totalBytes / sizeof(Data); if (index < 1 || index > totalItems) { std::cout << "无效的条目序号!\n"; return false; } std::streampos targetPos = (index - 1) * sizeof(Data); file.seekp(targetPos); // 移动写指针到目标位置 file.write(reinterpret_cast<const char*>(&newData), sizeof(Data)); return true; }
在主菜单中增加修改数据选项并调用:
std::cout << "3. 修改指定数据\n"; // ... if (choice == 3) { int index; std::cout << "输入要修改的条目序号:"; std::cin >> index; Data newData; std::cin.ignore(); std::cout << "输入新字符串:"; std::cin.getline(newData.Hold1, 50); std::cout << "输入新数字:"; std::cin >> newData.Hold2; if (updateData(inventory, index, newData)) { std::cout << "数据修改成功!\n"; } }
完整修正后的代码
#include <iostream> #include <fstream> #include <cstring> struct Data { char Hold1[50]; int Hold2; }; void writeData(std::fstream& file, const Data& data) { file.seekp(0, std::ios::end); file.write(reinterpret_cast<const char*>(&data), sizeof(Data)); } bool readData(std::fstream& file, int index, Data& outData) { file.seekg(0, std::ios::end); std::streampos totalBytes = file.tellg(); int totalItems = totalBytes / sizeof(Data); if (index < 1 || index > totalItems) { std::cout << "无效的条目序号!\n"; return false; } std::streampos targetPos = (index - 1) * sizeof(Data); file.seekg(targetPos); file.read(reinterpret_cast<char*>(&outData), sizeof(Data)); return true; } bool updateData(std::fstream& file, int index, const Data& newData) { file.seekg(0, std::ios::end); std::streampos totalBytes = file.tellg(); int totalItems = totalBytes / sizeof(Data); if (index < 1 || index > totalItems) { std::cout << "无效的条目序号!\n"; return false; } std::streampos targetPos = (index - 1) * sizeof(Data); file.seekp(targetPos); file.write(reinterpret_cast<const char*>(&newData), sizeof(Data)); return true; } int main() { int choice; bool loop = true; std::fstream inventory; inventory.open("inventory.dat", std::ios::out | std::ios::in | std::binary); if (inventory.fail()) { // 文件不存在时创建新文件 inventory.open("inventory.dat", std::ios::out | std::binary); inventory.close(); inventory.open("inventory.dat", std::ios::out | std::ios::in | std::binary); if (inventory.fail()) { std::cout << "无法打开或创建文件!\n"; return 0; } } while (loop) { std::cout << "\n===== 二进制文件操作菜单 =====\n"; std::cout << "1. 写入数据\n"; std::cout << "2. 读取指定数据\n"; std::cout << "3. 修改指定数据\n"; std::cout << "4. 退出\n"; std::cout << "请输入选择:"; std::cin >> choice; switch (choice) { case 1: { Data test1; std::cin.ignore(); std::cout << "输入字符串(最多49字符):"; std::cin.getline(test1.Hold1, 50); std::cout << "输入数字:"; std::cin >> test1.Hold2; writeData(inventory, test1); std::cout << "数据写入成功!\n"; break; } case 2: { int index; std::cout << "输入要读取的条目序号:"; std::cin >> index; Data result; if (readData(inventory, index, result)) { std::cout << "\n第" << index << "条数据:\n"; std::cout << "字符串:" << result.Hold1 << "\n"; std::cout << "数字:" << result.Hold2 << "\n"; } break; } case 3: { int index; std::cout << "输入要修改的条目序号:"; std::cin >> index; Data newData; std::cin.ignore(); std::cout << "输入新字符串:"; std::cin.getline(newData.Hold1, 50); std::cout << "输入新数字:"; std::cin >> newData.Hold2; if (updateData(inventory, index, newData)) { std::cout << "数据修改成功!\n"; } break; } case 4: { loop = false; std::cout << "程序退出。\n"; break; } default: { std::cout << "无效选择,请重新输入!\n"; break; } } } inventory.close(); return 0; }
关键知识点说明
- 二进制文件核心操作:用
seekg(读指针)和seekp(写指针)定位文件位置,tellg获取当前指针位置以计算总条目数。 - 结构体存储要求:必须使用POD类型(普通旧数据类型),不能包含指针或非POD成员(如
std::string),否则二进制写入会丢失数据。若需使用std::string,需手动序列化字符串长度和内容。 - 文件打开模式:
std::ios::binary确保文件以二进制模式打开,避免系统自动转换换行符导致数据错位。
内容的提问来源于stack exchange,提问作者BigBear21
相关产品推荐
相关产品推荐

