3

我有一个对象列表,我想将它们存储在一个尽可能小的文件中,以便以后检索。我一直在仔细阅读本教程,并且开始(我认为)理解,但有几个问题。这是我正在使用的片段:

    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));

不编译?i 是一个迭代器(迭代器不是指针吗?)。或者干脆

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

为什么我仍然需要搞乱地址?我不是将数据存储在文件中吗?如果地址/值自行持续存在,为什么我不能只存储以纯文本分隔的地址,而不是稍后获取它们的值?

我还应该使用 .txt 扩展名吗?<edit>那我应该用什么代替呢?我尝试了 .dtb 并且无法创建该文件。</edit>实际上,我什至无法使用 ios::binary 标志打开文件而不会出现错误。我在传递文件名时也遇到了麻烦(作为字符串类字符串,由 c_str() 转换回来,它可以编译但会出错)。

抱歉这么多小问题,但基本上都概括为如何有效地将对象存储在文件中?

4

6 回答 6

7

您正在尝试做的事情称为序列化。Boost 有一个非常好的库来执行此操作。

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

(char*)&(*i)

说要获取迭代器 i,取消引用它以获取您的对象,获取它的地址并将其视为字符数组。这是写入文件的开始。 sizeof(Referral)是要写出的字节数。

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

于 2009-09-28T18:59:43.653 回答
2

问题 #1 为什么...无法编译?答案:因为 i 不是推荐人*——它是一个 list::iterator ;; 迭代器是指针的抽象,但它不是指针。

问题 #2 我还应该使用 .txt 扩展名吗?答:应该不会。许多系统将 .txt 与 MIME 类型 text/plain 相关联。

未问的问题:这行得通吗?答:如果推荐人有任何指针,则。当您尝试从文件中读取引用时,指针将指向内存中曾经存在的位置 不能保证那里有任何有效的东西,至少是指针指向的所有东西原来。当心。

于 2009-09-28T18:58:56.720 回答
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 库)也是如此。如果您仍在学习,那么我建议您尝试两种方法来了解它们的工作原理以及最适合您的目的的方法。

于 2009-09-29T00:16:28.490 回答
1

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

于 2009-09-28T19:00:01.563 回答
1

您正在尝试做的事情(从文件读取和写入原始内存)将调用未定义的行为,会因非普通旧数据类型而中断,并且生成的文件将取决于平台,编译器依赖,甚至可能依赖于编译器设置。

C++ 没有任何内置的序列化复杂数据的方法。但是,有些库您可能会觉得有用。例如:

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

于 2009-09-28T18:57:51.287 回答
0

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

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

^ 此行将对象的地址转换为 char 指针。

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

^ 此行试图将对象 (i) 转换为 char 指针(无效)

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

^ 当它需要一个 char 指针时,这一行正在传递写入一个对象。

于 2009-09-28T18:57:14.383 回答