You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

二进制文件存储含map<string,int>的student类对象报错求助

解决包含std::map<string, int>的类对象二进制读写错误问题

问题根源

你直接用ofstream::write和ifstream::read读写student类对象的方式之所以出错,核心原因是:

  • std::map<string, int>和std::string属于非POD(Plain Old Data)类型,它们的内部结构包含指向堆内存的指针(比如std::string存储的是字符数组的地址,std::map是树形节点的指针)。
  • 直接写入对象内存时,只会保存这些指针的地址值,而非指针指向的实际数据;读取时,这些地址在当前进程中是无效的,访问就会触发未定义行为(崩溃、乱码等)。
  • 换成字符数组能正常工作,是因为字符数组是POD类型,内存中存储的就是实际数据,没有堆指针依赖。

解决方案:手动序列化/反序列化

要正确读写包含非POD类型的类对象,需要手动实现序列化(将对象数据转换为可存储的二进制格式)和反序列化(从二进制数据还原对象)逻辑,把复杂类型拆解为基础数据逐个处理。

修改后的完整代码

#include <iostream>
#include <fstream>
#include <map>
#include <cstring>
#include <string>
using namespace std;

class student
{
public:
    int rollno;
    char name[50];
    int sub[10];
    map<string, int> marks;

    // 序列化:将对象数据写入文件流
    void serialize(ofstream& ofs) const {
        // 直接写入POD类型成员
        ofs.write(reinterpret_cast<const char*>(&rollno), sizeof(rollno));
        ofs.write(name, sizeof(name));
        ofs.write(reinterpret_cast<const char*>(sub), sizeof(sub));
        
        // 先写入map的元素数量,方便读取时循环
        size_t map_size = marks.size();
        ofs.write(reinterpret_cast<const char*>(&map_size), sizeof(map_size));
        
        // 逐个写入map的键值对
        for (const auto& kv : marks) {
            // 写入string的长度,再写入字符内容
            size_t str_len = kv.first.size();
            ofs.write(reinterpret_cast<const char*>(&str_len), sizeof(str_len));
            ofs.write(kv.first.c_str(), str_len);
            // 写入对应的分数
            ofs.write(reinterpret_cast<const char*>(&kv.second), sizeof(kv.second));
        }
    }

    // 反序列化:从文件流读取数据还原对象
    void deserialize(ifstream& ifs) {
        // 读取POD类型成员
        ifs.read(reinterpret_cast<char*>(&rollno), sizeof(rollno));
        ifs.read(name, sizeof(name));
        ifs.read(reinterpret_cast<char*>(&sub), sizeof(sub));
        
        // 读取map的元素数量
        size_t map_size;
        ifs.read(reinterpret_cast<char*>(&map_size), sizeof(map_size));
        
        // 清空原有数据,避免残留
        marks.clear();
        for (size_t i = 0; i < map_size; ++i) {
            // 读取string的长度和内容
            size_t str_len;
            ifs.read(reinterpret_cast<char*>(&str_len), sizeof(str_len));
            string subject(str_len, '\0');
            ifs.read(&subject[0], str_len);
            // 读取分数
            int score;
            ifs.read(reinterpret_cast<char*>(&score), sizeof(score));
            marks.emplace(subject, score);
        }
    }
};

int main()
{
    int totalsub;
    student st;
    st.rollno = 21;
    strcpy(st.name, "Aman Tiwari");
    cout << "enter total subject :";
    cin >> totalsub;
    for (int i = 0; i < totalsub; i++)
    {
        int num;
        string str;
        cin.ignore();
        cout << "enter " << i << " subject name :";
        getline(cin, str);
        cout << "enter marks:";
        cin >> num;
        st.marks.insert(pair<string, int>(str, num));
    }

    // 序列化写入文件
    ofstream ofobj("test.dat", ios::binary | ios::trunc);
    st.serialize(ofobj);
    ofobj.close();

    // 反序列化读取文件
    ifstream ifobj("test.dat", ios::binary);
    student temp;
    temp.deserialize(ifobj);
    ifobj.close();

    cout << " the data is " << endl;
    cout << temp.rollno << endl;
    cout << temp.name << endl;
    for (auto &mark : temp.marks)
    {
        cout << mark.first << " " << mark.second << endl;
    }

    cout << "After showing !!";
    return 0;
}

代码说明

  1. POD成员处理:rollno、name、sub都是POD类型,可直接用write/read读写内存。
  2. std::map处理:先写入元素数量,再逐个处理每个键值对,确保读取时知道要循环多少次。
  3. std::string处理:先写入字符串的长度,再写入字符内容,避免读取时无法确定字符串边界。
  4. 文件打开模式:写入时用ios::trunc覆盖旧文件(如果需要追加可以保留ios::app,但反序列化时要注意处理多个对象)。

其他可选方案

  • 如果可以使用第三方库,Boost.Serialization提供了更便捷的序列化机制,无需手动编写每个成员的读写逻辑。
  • C++20及以上可考虑使用std::format配合文本存储(非二进制),但二进制存储效率更高。

内容的提问来源于stack exchange,提问作者Aman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:05:22