2009-09-28 95 views
3

我有一个对象列表,我想存储在一个尽可能小的文件中以便以后检索。我一直在仔细阅读this tutorial,并开始(我认为)了解,但有几个问题。这里是我一起工作的片段:C++在一个文件中存储对象

static bool writeHistory(string fileName) 
{ 
    fstream historyFile; 
    historyFile.open(fileName.c_str(), ios::binary); 
    if (historyFile.good()) 
    { 
     list<Referral>::iterator i; 
     for(i = AllReferrals.begin(); 
       i != AllReferrals.end(); 
       i++) 
     { 
      historyFile.write((char*)&(*i),sizeof(Referral)); 
     } 
     return true; 
    } else return false; 
} 

现在,这是从片段

file.write((char*)&object,sizeof(className)); 

本教程所改编。现在我相信它正在做的是将对象转换为一个指针,取值和大小并将其写入文件。但是如果这样做,为什么还要花费这些转换呢?为什么不从一开始就拿这个价值?为什么它需要大小?此外,从我的理解那么,为什么

historyFile.write((char*)i,sizeof(Referral)); 

不能编译?我是一个迭代器(而不是一个指针的迭代器?)。或干脆

historyFile.write(i,sizeof(Referral)); 

为什么我需要乱搞地址呢?我不是将数据存储在文件中吗?如果地址/值自己保持不变,为什么我不能将地址以纯文本格式存储,并且稍后再取其值?

而且我还应该使用.txt扩展名吗? <编辑>我应该用什么来代替呢?我试过.dtb,但无法创建文件。 < /编辑>我实际上甚至不能使用ios :: binary标志打开没有错误的文件。我也无法传递文件名(作为一个字符串类字符串,由c_str()转换回来,它编译但给出错误)。

对不起,有这么多的小问题,但它基本上总结到如何有效地存储对象在文件中?

+0

此外,序列化是否会显着提升存储效率,因此它比纯文本存储更有价值?我意识到这取决于,但相对? – 2009-09-28 19:07:13

回答

0

Fstream.write只是将原始数据写入文件。第一个参数是一个指向数据起始地址的指针。第二个参数是对象的长度(以字节为单位),因此写入知道要写入多少个字节。

file.write((char*)&object,sizeof(className)); 

^ 这条线对象的地址转换为字符指针。

historyFile.write((char*)i,sizeof(Referral)); 

^ 这条线是试图一个目标(i)转换成字符指针(无效)

historyFile.write(i,sizeof(Referral)); 

^ 这条线通过写入一个对象,当它期望一个char指针。

+0

在第二个片段中,我不是一个对象吗?它是一个迭代器。迭代器是一个对象吗? – 2009-09-28 18:59:48

+0

我需要二进制标志吗?当写一个对象时,这会发生什么变化? – 2009-09-28 19:00:37

+0

并再次感谢! – 2009-09-28 19:01:12

1

你正在尝试做什么(从文件中读取和写入原始内存)会调用未定义的行为,会破坏任何不是普通旧数据类型的东西,并且生成的文件将会依赖于平台,依赖于编译器,甚至可能依赖于编译器设置。

C++没有任何内置的序列化复杂数据的方式。不过,您可能会发现有用的库。例如:

http://www.boost.org/doc/libs/1_40_0/libs/serialization/doc/index.html

2

问题1为什么......不能编译? 答案:因为我不是引用* - 它是一个list :: iterator ;;一个迭代器是一个指针上的抽象,但它不是一个指针。

问题2我应该仍然使用.txt扩展名吗? 答:可能不是。 .txt被许多系统与MIME类型的text/plain关联。

未询问问题:这是行不通的吗? 答案:如果转介有任何指示,。当您尝试从文件中读取的介绍人,指针将指向哪里的东西使用现场在内存中的位置,但也不能保证有什么有效存在了,至少所有这些指针最初指向的东西。小心。

7

你试图做的就是所谓的序列化。 Boost有一个very good library这样做。

在某些情况下,您想要做的事情可以在一些非常重要的条件下起作用。它只适用于POD类型。它只能保证适用于使用相同版本的编译器编译的代码,并且使用相同的参数。

(char*)&(*i)

说采取迭代器I,取消对它的引用让你的对象,利用它的地址,并把它当作一个字符数组。这是正在写入文件的开始。 sizeof(Referral)是将被写出的字节数。

不,一个迭代器不一定是一个指针,尽管指针满足迭代器的所有需求。

+0

我认为在哪里我很困惑是取消引用和采取地址之间的区别。我认为当你使用地址而不是它的值时,一个对象被“引用”了。需要解引用,然后采取对象的地址是混淆,因为它看起来像你只是在一个循环。你介意澄清这个区别吗?再次感谢! – 2009-09-28 19:05:05

+0

那么为什么我一直在使用迭代器而不是指针呢? – 2009-09-28 19:11:12

+0

认为解引用和“接收地址”是相反的方向。如果你有一个对象,并且你的地址,你现在有一个指针。如果你有一个指针并将其解引用它,那么你就拥有了原始对象。如果你拿一个指针的地址,你现在有一个指向指针的指针。您需要解除引用它两次以获取原始对象。 – KeithB 2009-09-28 19:26:16

1

您是否已经看过boost::serialization,它很健壮,有很好的文档,支持版本控制,如果您想切换到XML格式而不是二进制格式,它会更容易。

+0

这是一个很好的建议。 – Massa 2009-09-28 19:13:51

2

是不是一个迭代器的指针?

迭代器就像外部的指针一样。在大多数情况下(也许是所有情况),它实际上是某种形式的对象,而不是一个空指针。迭代器可能包含一个指针作为内部成员变量,它用于执行其作业,但它也可能包含其他内容或其他变量(如有必要)。

此外,即使迭代器内部有一个简单的指针,它可能并不直接指向您感兴趣的对象。它可能指向容器类使用的某种簿记组件,然后它可以用于获取感兴趣的实际对象。幸运的是,我们不需要关心这些内部细节实际上是什么。

因此,考虑到这一点,这里是(char*)&(*i)发生了什么。

  • *i返回对存储在列表中的对象的引用。
  • &取得该对象的地址,从而产生一个指向该对象的指针。
  • (char*)将该对象指针转换为char指针。

这代码片段会做这样的事情的缩写形式:

Referral& r = *i; 
Referral* pr = &r; 
char* pc = (char*)pr; 

为什么我需要无论如何搞乱地址各地 ?

为什么它需要大小?

fstream::write旨在将一系列字节写入文件。它不知道这些字节意味着什么。您给它一个地址,以便它可以写入从该地址指向的任何地方开始存在的字节。您给它一个大小,以便知道要写入多少个字节。

所以,如果我做的:

MyClass ExampleObject; 
file.write((char*)ExampleObject, sizeof(ExampleObject)); 

然后将它写入直接存在的文件中ExampleObject的所有字节。

注:正如其他人所提到的,如果你想要写的对象有成员,动态分配内存或以其它方式使用指针,则指向的内存不会被一个简单的通话fstream::write写。


将序列化给存储效率的提升显著?

从理论上讲,二进制数据通常可以小于普通文本,读写速度更快。在实践中,除非您处理的数据量非常大,否则您可能永远都不会注意到其中的差异。现在硬盘很大,而且处理器速度很快。

和效率是不是唯一要考虑的事情:

  • 二进制数据是难以检查,调试,并根据需要修改。至少没有附加工具,但即使如此,纯文本仍然通常更容易。
  • 如果您的数据文件将在您的程序的不同版本之间持续存在,那么如果您需要更改对象的布局会发生什么情况?编写代码让版本2程序可以读取版本1文件中的对象会让人恼火。此外,除非您提前采取行动(如通过向文件中写入版本号),那么读取版本2文件的版本1程序可能存在严重问题。
  • 您是否需要验证数据?例如,反对腐败或恶意更改。在这样的二进制方案中,你需要编写额外的代码。而使用纯文本时,转换例程通常可以帮助填充验证卷。

当然,一个好的序列化库可以帮助解决其中的一些问题。一个好的纯文本格式库(例如XML的库)也是如此。如果您还在学习,那么我会建议您尝试两种方式来了解它们的工作方式以及可能对您的目的最有效的方法。