C++随机访问文件

3

这是我第一次尝试在C++中使用随机访问文件,虽然我之前在Java中也尝试过,但是我无法在C++中使它正常工作。我的想法是创建100个空记录,然后将一个记录存储在文件的特定记录号处。以下是我的代码,我尽可能地保持简单:

这里我有一个名为Tool的结构体:

struct Tool {
    int number;
    char name[20];
    int quantity;
    double cost;
};

这里是我的主函数内容:

fstream outFile;
outFile.open("inventory.dat");

// Create 100 empty tool records
Tool tool;
tool.number = 0;
tool.name[0] = '\0';
tool.quantity = 0;
tool.cost = 0;  

for (int i = 0; i < 100; i++) {
    outFile.write(reinterpret_cast<char *>(&tool.number), sizeof(int));
    outFile.write(tool.name, sizeof(char)* 20);
    outFile.write(reinterpret_cast<char *>(&tool.quantity), sizeof(int));
    outFile.write(reinterpret_cast<char *>(&tool.cost), sizeof(double));
}

// Insert A tool record
Tool t;
t.number = 3;
t.quantity = 7;
t.cost = 57;
strcpy(tool.name, "Electric Sander");

outFile.seekp((tool.number - 1) * sizeof(Tool));
outFile.write(reinterpret_cast<char *>(&tool.number), sizeof(int));
outFile.write(tool.name, sizeof(char)* 20);
outFile.write(reinterpret_cast<char *>(&tool.quantity), sizeof(int));
outFile.write(reinterpret_cast<char *>(&tool.cost), sizeof(double));

outFile.close();

我初始化100个空记录的部分很好(假设我们要评论代码的插入部分)。

然而,当执行插入部分时,我的程序会生成4GB的数据。我不确定发生了什么。非常感谢任何形式的帮助。提前致谢。


你没有考虑成员之间的填充。seekp 操作使用 sizeof(Tool),但写操作使用各个成员的大小。 - Thomas Matthews
另外,您没有使用二进制模式。这似乎不是您的意图... - Deduplicator
3
打开文件时应设置二进制模式:outFile.open("inventory.dat", std::ios::binary); - Galik
如果你的代码包含 reinterpret_cast,那么它是错误的,除非你能解释为什么它不是错误的。 - Neil Kirk
3个回答

3
您可以编写结构体的全部内容。
    outFile.seekp(t.number*sizeof(Tool));
    outFile.write(reinterpret_cast<char *>(&tool),sizeof(Tool));

不要忘记告诉编译器不要插入填充。
#ifdef MSVC
#pragma pack(push,1)
#endif
struct Tool {
    int number;
    char name[20];
    int quantity;
    double cost;
#ifdef GCC
}__attribute__((packed));
#else
};
#endif

#ifdef MSVC
#pragma pack(pop)
#endif

资料来源:
https://codereview.stackexchange.com/questions/26344/writing-reading-data-structure-to-a-file-using-c
https://dev59.com/AWMl5IYBdhLWcg3wJUEC#18654265
我们尊贵的会员们的评论。


1
注意:这也会写出成员之间的任何填充。 - Thomas Matthews
1
在执行此操作之前,您需要使用您最喜欢的编译器的特定于编译器的方式显式设置成员对齐方式,以确保文件格式一致且可预测。 - Jason C
1
不错的编辑;注意对于MSVC(为了与你的GCC属性相当),你实际上需要在之前使用#pragma pack(push,1),并在之后使用#pragma pack(pop),以恢复默认对齐方式。否则它会影响该行之后的所有内容 - Jason C
1
我感到很惭愧,因为我是一名C#程序员,只是想尝试回答一个最初看起来很简单的C++问题。请随意编辑或告诉我删除这个“答案” :) - Tony
1
别担心,“C++”和“简单”通常不会出现在同一个句子中。 - Jason C
太棒了,我不知道我可以直接写一个结构体(就像一个序列化对象)...感谢您的帮助。 - it2051229

2
在最后一部分中,您使用了tool而实际上应该使用t。具体来说:
outFile.seekp((tool.number - 1) * sizeof(Tool));

应该是:

outFile.seekp((t.number - 1) * sizeof(Tool));

除了最后的所有tool.字段(假设您想要使用t)外,此时tool.number为0,因此tool.number-1为-1。如果pos_type是32位无符号整数,则包装值将使您请求的位置约为4GB。
Thomas Matthews在他的评论中提出的对齐点以及Tony在他的答案中详细说明的对齐点很重要,以确保文件中的数据正确。
Deduplicator和Galik在评论中提出的有关二进制模式的观点也很重要,以确保数据被正确写入。

哦,我的错。我在编码时一团糟,尝试了所有可能的代码,直到放弃并在这里发布问题之前都没有适当地命名它们。这很有帮助。非常感谢。 - it2051229

1
这是一个备选方案:
struct Tool
{
    int number;
    char name[20];
    int quantity;
    double cost;
    void binary_write(std::ostream& out) const
    {
      out.write((char *) &number, sizeof(number));
      out.write((char *) &name[0], sizeof(name);
      out.write((char *) &quantity, sizeof(quantity));
      out.write((char *) &cost, sizeof(cost));
    }
    void binary_read(std::istream& inp)
    {
      inp.read((char *) &number, sizeof(number));
      inp.read((char *) &name[0], sizeof(name);
      inp.read((char *) &quantity, sizeof(quantity));
      inp.read((char *) &cost, sizeof(cost));
    }
    size_t binary_size(void) const
    {
      return sizeof(number) + sizeof(name)
             + sizeof(quantity) + sizeof(cost);
    }
};

int main(void)
{
  std::ofstream outfile("test.dat", ios::binary);
  if (!outfile)
  {
    cerr << "Error opening test.dat\n";
    return 1;
  }
  Tool t;
  for (unsigned int i = 0; i < 100; ++i)
  {
    t.binary_write(outfile);
  }
  outfile.close();
  std::ifstream in_file("test.dat", ios::binary)
  if (!in_file)
  {
    cerr << "Error opening test.dat for reading\n";
    return 2;
  }
  in_file.seekg(10 * t.binary_size(), ios::beg);
  t.binary_read(in_file);
  return 0;
}  

这里的概念是将读取和写入功能放入对象中,因为对象知道其成员,并且可以将数据隐藏在其他对象之外(这是一件好事)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接