首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

C++ 課程設計:學生成績管理系統文件存儲方案 (變長數組序列化)

     編輯:程式設計綜合問答

問題場景

C++ 學生成績管理系統,`Student` 類包含動態數量的 `Course` 對象數組。需將對象持久化到文件,後續讀取還原。

核心難點:變長數組序列化

  • 每個學生選課數不同 → `Course` 數組長度不固定。
  • C++ 對象含 `std::string`、`std::vector` 等動態成員,**不能直接 `write((char*)this, sizeof(*this))`** (淺拷貝指針、內存佈局不可移植)。
  • 需設計**自定義二進制/文本協議**,顯式序列化每個字段。

推薦方案:長度前綴 + 逐字段寫入 (二進制高效) 或 JSON (文本可讀)

方案一:二進制協議 (高性能、定長字段優先)


// 文件結構:
// [Student Count: int32]
// 循環 Student Count 次:
//   [Student ID: int32] [Name Len: uint16] [Name: char[]] [Course Count: uint16]
//   循環 Course Count 次:
//     [Course ID: int32] [Name Len: uint16] [Name: char[]] [Credit: float] [Grade: float]

#include 
#include 
#include 
#include 

struct Course {
    int32_t id;
    std::string name;
    float credit;
    float grade;
};

struct Student {
    int32_t id;
    std::string name;
    std::vector courses;
};

// 寫入單個字符串:長度 + 內容
void writeString(std::ofstream &ofs, const std::string &s) {
    uint16_t len = static_cast(s.size());
    ofs.write(reinterpret_cast(&len), sizeof(len));
    ofs.write(s.data(), len);
}

// 讀取字符串
std::string readString(std::ifstream &ifs) {
    uint16_t len;
    ifs.read(reinterpret_cast(&len), sizeof(len));
    std::string s(len, '\0');
    ifs.read(&s[0], len);
    return s;
}

// 序列化 Student
void saveStudents(const std::vector &students, const char *filename) {
    std::ofstream ofs(filename, std::ios::binary);
    int32_t count = students.size();
    ofs.write(reinterpret_cast(&count), sizeof(count));
    for (const auto &stu : students) {
        ofs.write(reinterpret_cast(&stu.id), sizeof(stu.id));
        writeString(ofs, stu.name);
        uint16_t ccount = stu.courses.size();
        ofs.write(reinterpret_cast(&ccount), sizeof(ccount));
        for (const auto &c : stu.courses) {
            ofs.write(reinterpret_cast(&c.id), sizeof(c.id));
            writeString(ofs, c.name);
            ofs.write(reinterpret_cast(&c.credit), sizeof(c.credit));
            ofs.write(reinterpret_cast(&c.grade), sizeof(c.grade));
        }
    }
}

// 反序列化
std::vector loadStudents(const char *filename) {
    std::ifstream ifs(filename, std::ios::binary);
    std::vector students;
    int32_t count;
    ifs.read(reinterpret_cast(&count), sizeof(count));
    students.reserve(count);
    for (int i = 0; i < count; ++i) {
        Student stu;
        ifs.read(reinterpret_cast(&stu.id), sizeof(stu.id));
        stu.name = readString(ifs);
        uint16_t ccount;
        ifs.read(reinterpret_cast(&ccount), sizeof(ccount));
        stu.courses.reserve(ccount);
        for (int j = 0; j < ccount; ++j) {
            Course c;
            ifs.read(reinterpret_cast(&c.id), sizeof(c.id));
            c.name = readString(ifs);
            ifs.read(reinterpret_cast(&c.credit), sizeof(c.credit));
            ifs.read(reinterpret_cast(&c.grade), sizeof(c.grade));
            stu.courses.push_back(c);
        }
        students.push_back(std::move(stu));
    }
    return students;
}

方案二:JSON (nlohmann/json) — 現代首選,可讀、易調試、跨語言


#include 
using json = nlohmann::json;

void saveStudentsJson(const std::vector &students, const char *filename) {
    json j = students; // 自動序列化 (需為 struct 定義 to_json/from_json 或用 NLOHMANN_DEFINE_TYPE_INTRUSIVE)
    std::ofstream ofs(filename);
    ofs << j.dump(4); // 美化縮進
}

std::vector loadStudentsJson(const char *filename) {
    std::ifstream ifs(filename);
    json j = json::parse(ifs);
    return j.get>();
}

// 為 Student/Course 定義序列化 (C++17 結構化綁定)
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Course, id, name, credit, grade)
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Student, id, name, courses)

關於 `write()`/`read()` 直接存對象的誤區

「輸出流函數 write() 和輸入流函數 read() 可以存對象嗎?」

絕對不能**用於含非 POD 類型 (std::string, std::vector, 虛函數類) 的類**。

  • 會將 `std::string` 內部指針 (指向堆內存) 寫入文件,讀取後指針失效 → 崩潰/亂碼。
  • 虛函數表指針 (vptr) 寫入文件無意義,進程重啟後地址失效。
  • 記憶體佈局受編譯器、優化選項、ABI 影響,**不可跨進程/跨編譯器移植**。
  • **僅適用於**:純 POD 結構體 (僅含定長基本類型、固定數組、無指針、無虛函數)。

最佳實踐總結

  1. **首選 JSON (nlohmann/json)**:開發效率最高、調試直觀、跨語言互操作、支持 schema 驗證、現代 C++ 項目標配。
  2. **性能敏感/海量數據** 用二進制協議 (或 Protocol Buffers / FlatBuffers / Cap'n Proto),定義 `.proto` 自動生成代碼,嚴格 schema 演進。
  3. **拒絕 `write(this, sizeof(*this))`**:除非是 C 風格 POD 結構體且極其確定不擴展。
  4. **版本兼容**:文件頭寫入 `magic number` + `version`,讀取時按版本分派解析邏輯。

最佳回答核心思路:「每個課程類的大小應該是一樣的吧。就像往文件裡寫結構體一樣、一把寫進去,你的程序輸出文件如果是一個學生一個,就可以把這個學生選了幾門課做一個計數、寫在文件開頭,在讀取文件的時候就可以先讀取一個 int,然後再讀取課程信息啦」—— 給出了長度前綴序列化的核心思想。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved