C++中如何用ofstream的write函数从函数写入数据到文件
C++中使用write函数实现类对象文件存储的问题解答
一、你的代码存在的核心问题
你当前直接用myfile.write((char *)&inform, sizeof(studentDetails))写入studentDetails对象的做法是错误的——因为类中的std::string studentName是动态分配内存的类型,它内部只保存了指向堆内存的指针,写入文件的只是这个指针的地址,而非实际的字符串内容。后续读取时,这个地址在新的进程空间中毫无意义,会直接导致数据损坏或程序崩溃。
二、write函数的适用场景
ofstream::write的作用是将连续内存块中的二进制数据原封不动写入文件,它只适合存储满足以下条件的类:
- 所有成员都是POD类型(Plain Old Data,比如int、char、double、固定长度数组等,无动态内存分配、无虚函数)
- 若存在继承关系,基类不能包含虚函数(否则对象会携带虚表指针,写入后读取会失效)
三、修正代码的两种方案
方案1:拆分序列化(支持std::string)
针对包含std::string的类,需要手动拆分成员写入:先存字符串长度,再存字符串内容,最后写入其他POD成员。同时要注意处理cin输入后的换行符残留问题:
#include <iostream> #include <fstream> #include <string> using namespace std; class studentDetails { public: string studentName; int age; char grade; int birthYear; void getDetails(); }; void studentDetails::getDetails() { cout << "Enter the correct data in the forms below\n"; cout << "Enter the full names of the student\n"; getline(cin, studentName); cout << "Enter the age of the student\n"; cin >> age; cout << "Enter the student's grade (single character)\n"; cin >> grade; cout << "Enter the year of birth\n"; cin >> birthYear; } void captureStudentDetails() { studentDetails inform; cin.ignore(); // 忽略之前cin操作留下的换行符,避免getline读空 inform.getDetails(); // 必须以二进制模式打开文件 ofstream myfile("studentDetails.txt", ios::out | ios::app | ios::binary); // 先写入字符串长度 size_t nameLength = inform.studentName.size(); myfile.write(reinterpret_cast<char*>(&nameLength), sizeof(nameLength)); // 再写入字符串内容 myfile.write(inform.studentName.c_str(), nameLength); // 写入其他POD成员 myfile.write(reinterpret_cast<char*>(&inform.age), sizeof(inform.age)); myfile.write(reinterpret_cast<char*>(&inform.grade), sizeof(inform.grade)); myfile.write(reinterpret_cast<char*>(&inform.birthYear), sizeof(inform.birthYear)); myfile.close(); } int main() { cout << "Welcome to the student database\n"; captureStudentDetails(); return 0; }
对应的读取操作也要严格按照这个顺序:先读字符串长度,再读对应长度的字符生成std::string,最后读取其他成员。
方案2:替换为固定长度字符数组(简化写入)
如果可以修改类定义,把std::string换成固定长度的char数组,让整个类成为POD类型,就能直接用write写入:
#include <iostream> #include <fstream> using namespace std; class studentDetails { public: char studentName[100]; // 固定长度数组,最多存99个字符+结束符 int age; char grade; int birthYear; void getDetails(); }; void studentDetails::getDetails() { cout << "Enter the correct data in the forms below\n"; cout << "Enter the full names of the student\n"; cin.getline(studentName, 100); cout << "Enter the age of the student\n"; cin >> age; cout << "Enter the student's grade (single character)\n"; cin >> grade; cout << "Enter the year of birth\n"; cin >> birthYear; } void captureStudentDetails() { studentDetails inform; cin.ignore(); inform.getDetails(); ofstream myfile("studentDetails.txt", ios::out | ios::app | ios::binary); // 直接写入整个对象的内存块 myfile.write(reinterpret_cast<char*>(&inform), sizeof(studentDetails)); myfile.close(); } int main() { cout << "Welcome to the student database\n"; captureStudentDetails(); return 0; }
四、outFile.write(reinterpret_cast<char *> (&creation), sizeof(bankAccount))的含义
逐部分拆解:
&creation:获取bankAccount类型对象creation的内存地址,类型为bankAccount*reinterpret_cast<char*>:将对象地址强制转换为char*——因为write的第一个参数要求是const char*,它需要以字节为单位遍历内存块sizeof(bankAccount):获取整个bankAccount对象占用的内存字节数,告诉write要写入多少字节的数据- 整行代码作用:把
creation对象所在的连续内存块中的所有字节,以二进制形式原封不动写入outFile文件
五、ios::binary的作用
- 默认打开文件时是文本模式,操作系统会自动转换换行符:比如Windows下写入
\n会被替换为\r\n,读取时再转回来;Unix/Linux则无此转换。 ios::binary指定以二进制模式打开文件,操作系统不会对任何字符做转换,写入的每个字节都会原样保存到文件中,读取时也会原样读取。- 使用
write写入类对象时必须加ios::binary,否则文本模式的换行转换会破坏二进制数据的结构,导致读取后数据完全错误。
六、带继承关系的类的处理
如果你的类存在继承:
- 若基类无虚函数,且所有成员都是POD类型,派生类可以直接用
write写入(派生类的内存布局是基类成员在前,派生类成员在后,连续存储) - 若基类有虚函数,对象会包含虚表指针,此时不能直接用
write写入——因为虚表指针是进程内的地址,写入文件后读取到新进程中无效,调用虚函数会直接崩溃。这种情况必须用方案1的拆分序列化方式,手动写入每个成员。
内容的提问来源于stack exchange,提问作者alson
相关产品推荐
相关产品推荐

