首先在代码中,我将名称和手机号码存储在一个对象中,然后使用fstream.write()方法将该对象写入一个文本文件。它可以成功工作,但是当我将写入的内容读入另一个对象并调用display方法时,它将正确显示数据,但是在打印数据后却给我带来了分割错误。 这是我的代码-
#include<iostream>
#include<fstream>
using namespace std;
class Telephone
{
private:
string name="a";
int phno=123;
public:
void getTelephoneData()
{
cout<<"Enter Name:";
cin>>name;
cout<<"Enter Phone Number:";
cin>>phno;
}
void displayData()
{
cout<<"Name\t\tPhone no"<<endl;
cout<<name<<"\t\t"<<phno<<endl;
}
void getData() {
Telephone temp;
ifstream ifs("Sample.txt",ios::in|ios::binary);
ifs.read((char*)&temp,sizeof(temp));
temp.displayData();
}
};
int main()
{
Telephone t1;
t1.getTelephoneData();
cout<<"----Writing Data to file------"<<endl;
ofstream ofs("Sample.txt",ios::out|ios::binary);
ofs.write((char*)&t1,sizeof(t1));
ofs.close();
t1.getData();
}
请在错误之处帮助我。 预先谢谢...!
答案 0 :(得分:4)
因此,在我给您提供解决方案之前,让我们简要介绍一下这里发生的事情:
ofs.write((char*)&t1,sizeof(t1));
您正在做的是将t1转换为指向char的指针,然后说“按原样写入to1的内存表示形式”。因此,我们不得不自问:t1的内存表示是什么?
写4字节整数可能没问题。它绝对不是可移植的(big-endian与little-endian),如果在具有不同字节序的平台上读取文件,则可能会得到错误的int值。
写std::string
绝对不行。字符串是复杂的对象,它们通常在堆上分配存储(尽管有诸如小字符串优化之类的东西)。这意味着您将序列化一个指向动态分配对象的指针。这永远都行不通,因为向后读取指针将指向您绝对无法控制的内存中的某个位置。这是未定义行为的一个很好的例子。任何事情都会发生,并且程序可能会发生任何事情,包括尽管存在严重问题,但“看起来工作正确”。
在您的特定示例中,由于创建的Telephone对象仍在内存中,因此您获得的是指向同一动态分配内存的2个指针。当您的temp
对象超出范围时,它将删除该内存。
返回主功能时,t1
超出范围时,它将尝试再次删除相同的内存。
对任何类型的指针进行序列化是一个很大的禁忌。如果对象内部由指针组成,则需要针对这些指针将如何存储在流中做出自定义解决方案,然后读取以构造一个新对象。常见的解决方案是“好像”将它们存储为按值存储,然后,当从存储中读取对象时,动态分配内存并将对象的内容放在同一内存中。如果尝试序列化多个对象指向内存中同一地址的情况,这显然将不起作用:如果尝试应用此解决方案,最终将获得原始对象的多个副本。
幸运的是,对于 std::string
而言,此问题很容易解决,因为字符串已使operator<<
和operator>>
重载,并且您无需执行任何操作即可使他们工作。
编辑:仅使用operator<<
和operator>>
不能用于std::string
,稍后再解释原因。
有许多可能的解决方案,我将在这里分享一个。 基本思想是,您应该分别序列化电话结构的每个成员,并依靠每个成员都知道如何序列化自身这一事实。我将忽略跨字节序兼容性的问题,以使答案更简短,但是如果您关心跨平台兼容性,则应考虑一下。
我的基本方法是为类电话改写operator<<
和operator>>
。
我声明了两个免费功能,它们是Telephone类的朋友。这将使他们可以戳入不同电话对象的内部,以序列化其成员。
class Telephone {
friend ostream& operator<<(ostream& os, const Telephone& telephone);
friend istream& operator>>(istream& is, Telephone& telephone);
// ...
};
编辑:最初,我有错误地序列化字符串的代码,所以我的评论很简单,就是明显的错误
用于实现功能的代码令人惊讶。因为遇到空白时,用于字符串的operator>>
会停止从流中读取数据,因此使用名称不是单个单词或带有特殊字符的名称将不起作用,并将流置于错误状态,从而导致无法读取电话号码。为了解决这个问题,我在@Michael Veksler后面跟随了这个示例,并明确存储了字符串的长度。我的实现如下所示:
ostream& operator<<(ostream& os, const Telephone& telephone)
{
const size_t nameSize = telephone.name.size();
os << nameSize;
os.write(telephone.name.data(), nameSize);
os << telephone.phno;
return os;
}
istream& operator>>(istream& is, Telephone& telephone)
{
size_t nameSize = 0;
is >> nameSize;
telephone.name.resize(nameSize);
is.read(&telephone.name[0], nameSize);
is >> telephone.phno;
return is;
}
请注意,您必须确保所写入的数据与稍后将要尝试读取的数据匹配。如果存储的信息量不同,或者参数的顺序错误,则最终不会得到有效的对象。如果以后要对Telephone类进行任何形式的修改,则通过添加要保存的新字段,您将需要修改两者函数。
要支持名称中包含空格的名称,还应修改从cin读取名称的方式。一种方法是使用std::getline(std::cin, name);
代替cin >> name
最后,如何从这些流中进行序列化和反序列化:
不要使用ostream::write()
和istream::read()
函数-改用我们已覆盖的operator<<
和operator>>
。
void getData() {
Telephone temp;
ifstream ifs("Sample.txt",ios::in|ios::binary);
ifs >> temp;
temp.displayData();
}
void storeData(const Telephone& telephone) {
ofstream ofs("Sample.txt",ios::out|ios::binary);
ofs << telephone;
}
答案 1 :(得分:1)
您不能简单地将std::string
个对象转储到文件中。请注意,std::string
被定义为
std::basic_string<char, std::char_traits<char>, std::allocator<char>>
当std::string
无法避免时,它使用std::allocator<char>
为字符串分配堆内存。通过将Telephone
对象与ofs.write((char*)&t1,sizeof(t1))
一起写入,您还在写std::string
它包含为一组位的对象。这些std::string
位中的某些位可以是从std::allocator
获得的指针。这些指针指向包含字符串字符的堆内存。
通过调用ofs.write()
,程序将写入指针,但不会写入字符。然后,当使用ifs.read()
读取字符串时,它具有指向未分配堆的指针,该指针不包含字符。即使它确实指向了一个有效的堆,从某种程度上讲,它仍然不会包含它应该具有的字符。有时您可能很幸运,并且程序不会因为字符串太短而避免堆分配而崩溃,但这是完全不可靠的。
您必须为此类编写自己的序列化代码,而不要依赖ofs.write()
。有几种方法可以做到这一点。首先,您可以使用boost serialization。您只需按照linked tutorial中的示例进行操作,即可进行序列化。
另一种选择是从头开始做所有事情。当然,最好使用现有的代码(例如boost),但是亲自实现它可能是一种很好的学习经验。通过实施自己,您可以更好地了解如何在后台进行提升:
void writeData(std::ostream & out) const {
unsigned size = name.size();
out.write((char*)&size, sizeof(size));
out.write(name.data(), size);
out.write((char*)&phno, sizeof(phno));
}
然后在getData
中以相同的顺序读取它。当然,您必须动态地将字符串分配给正确的大小,然后用ifs.read()
填充它。
与operator<<
用于字符串不同,此技术适用于任何类型的字符串。它适用于包含任何字符的字符串,包括空格和空字符(\0
)。 operator>>
技术不适用于带有空格的字符串,例如名字的姓氏组合,因为它停在空白处。
安全性是一个问题。如果数据不受您的控制,则可以使用它来入侵您的系统。在我的序列化示例中,更糟的是耗尽内存。内存不足可能是拒绝服务的攻击媒介,或更糟的是。也许您应该限制字符串的最大大小并管理错误。
要考虑的另一件事是跨系统的互操作性。并非所有系统都以相同的方式表示int
或long
。例如,在64位linux上,long
是8字节,而在MS-Windows上是4字节。最简单的解决方案是使用out<<size<<' '
来写出大小,但是请确保使用C语言环境,否则四位数的长度中可能包含逗号或点,这会破坏解析。