【问题标题】:How to write a 2D array into a file and back in C如何将二维数组写入文件并返回 C
【发布时间】:2017-12-15 17:08:14
【问题描述】:

我正在尝试将一个板(这是一个已动态分配的二维数组)保存到一个文件中,以便用户以后可以“加载”它并且他们可以再次使用该板。要保存电路板,用户必须输入s filename。为了获取用户输入,我有一个用于保存命令的 else if 语句:

else if (playerMove.command == 's') {
    char* fileName = NULL;
    scanf(" %s", fileName);
    implementSave(boardState, fileName);

由于我们只学习了如何在 C 中读取/打开/写入二进制文件,因此我尝试在我的 implementSave 函数中对此做同样的事情:

void implementSave(BoardState* boardState, char* fileName) {
  FILE* file = fopen(fileName, "wb");
  fwrite(fileName, sizeof(char), sizeof(boardState->board.theBoard), file);
  fclose(file);
}

注意:我在我的程序中使用了一些结构;结构体board 包含char** theBoard, int numRows, int numCols, char blankSpace

但是,当我尝试运行保存命令时,这给了我一个错误。谁能指出我正确的方向?

【问题讨论】:

  • 什么样的错误?如果只是没有正确保存,那是因为 sizeof a char** 是 4 或 8(或类似的东西)。您应该计算要直接保存或加载的字符单元数:boardState->board.numRows*boardState->board.numCols
  • char* fileName = NULL; scanf(" %s", fileName); -- 您没有为 fileName... 分配存储空间。另外请注意,%s 指令 自动 会跳过前导空格,所以没有格式字符串中需要前导空格。
  • 取决于BoardState 是什么。但是fwrite(fileName, sizeof(char), sizeof(boardState->board.theBoard), file); 看起来完全错误。你的意思是fwrite(boardState, sizeof(char), sizeof(boardState->board.theBoard), file);
  • 如果theBoardchar **,您将无法写出,因为theBoard 的每个元素都是一个指针,它没有意义 - 您需要写出每一行分别
  • 您的二维数组是如何/在哪里声明的?见How to create a Minimal, Complete, and Verifiable example。对于type **指向类型指针的指针),您应该将值的总数写为文件中的第一个值(可能还有大小——在那之前的char, short, int..),然后您必须为每个指针调用一次fwrite 才能写入值。根据您的声明,不能保证这些值在内存中是连续的。

标签: c arrays file


【解决方案1】:

这段代码

 fwrite(fileName, sizeof(char), sizeof(boardState->board.theBoard), file);

意思是:

将数据写入文件file。数据位于fileName 指向的内存位置,数据由sizeof(boardState->board.theBoard) 个项目组成,每个项目都是sizeof(char) 字节大。

你觉得这对吗?我不这么认为;在fileName指向的位置可以找到数据已经是错误的,因为你想将板数据写入文件而不是文件名,对吧?

还要小心sizeof()sizeof() 不能动态确定某些内存的内容,它只知道静态内存的大小。例如

char test[20];
size_t s = sizeof(test);

s 将是 20。但现在考虑一下:

char test[20];
char * ptr = test;
size_t s = sizeof(ptr);

现在s 将是 4 或 8,因为指针的大小通常为 4 字节或 8 字节。 sizeof() 在这里为您提供指针的大小,而不是指针指向的内存大小。在 C 中没有办法获得指针指向的内存块的大小,这个大小必须总是已知的。

你说你的板子是char ** theBoard,所以 theBoard 是一个指向内存或字符指针数组的指针。

theBoard -> [0] -> ['a1', 'b1', 'c1', 'd1', ... ] 
            [1] -> ['a2', 'b2', 'c2', 'd2', ... ] 
            [2] -> ['a3', 'b3', 'c3', 'd3', ... ]
            :

在这种情况下,创建代码需要看起来像这样(假设 [0]、[1]、... 是行):

 theBoard = calloc(numberOfRows, sizeof(char *));
 for (size_t i = 0; i < numberOfRows; i++) {
     theBoard[i] = calloc(numberOfCols, sizeof(char));
 }

如果是这种情况,您需要逐行写入数据:

for (size_t row = 0; row < numberOfRows; row++) {
    fwrite(boardState->board.theBoard[row], sizeof(char), numberOfCols, file);
}

当然,假设所有行都有相同数量的列。

如果我可以给你一个提示,请不要创建板 char **,只需创建 char *,因为它让一切变得如此简单。看,如果你的板是 20x30(20 行,30 列),那么你可以这样定义你的板:

char * theBoard = calloc(numberOfRows * numberOfCols, sizeof(char))

现在你只有一个数组,像这样:

theBoard -> ['a1', 'b1', 'c1', 'd1', ... ,
             'a2', 'b2', 'c2', 'd2', ... ,
             'a3', 'b3', 'c3', 'd3', ... ,
             :
             ]

您将如何访问特定字段?很简单:

 int row = 5;
 int col = 8;
 char field = theBoard[(row * numberOfCols) + col];

然后你可以在一个调用中编写整个板:

fwrite(theBoard, sizeof(char), numberOfRows * numberOfCols, file);

看,容易多了。您也可以通过调用free(theBoard); 来释放整个电路板,而使用char ** 方法时,您必须这样做:

 for (size_t i = 0; i < numberOfRows; i++) {
     free(theBoard[i]);
 }
 free(theBoard);

【讨论】:

    【解决方案2】:

    您不能只将双指针写入文件。您必须索引第一个指针中的每个元素,并将数据写入成块的行(或列,但我已经演示了行)。为此,请使用 for 循环。请注意(假设板是 all 您要保存到 binary 文件)生成的文件大小应为 numRow * numCol 字节。

    int i;
    for (i = 0; i < BoardState->board.numRow; ++i) {
             fwrite(BoardState->board.theBoard[i], sizeof(char), BoardState->board.numCol, file)
    }
    

    读取(“加载”)文件与上面的代码相反 - 请注意,仅将板保存到文件是不明智的 - 我建议在文件的开头保存行数和列数在读取(加载)文件时轻松分配内存。

    因此,假设您将行数/列数存储在 int 中,则上述代码变为

    fwrite(&BoardState->board.numRow, sizeof(int), 1, file);
    fwrite(&BoardState->board.numCol, sizeof(int), 1, file);
    int i;
    for (i = 0; i < BoardState->board.numRow; ++i) {
             fwrite(BoardState->board.theBoard, sizeof(char), BoardState->board.numCol, file)
    }
    

    注意这有可能引入字节顺序问题。如果你有兴趣,可以谷歌一下。

    【讨论】:

    • 如果您修复对 fwrite() 的调用以正确使用参数,我将支持您的答案。
    • @Dúthomhas 没听懂!谢谢 - 很难在移动设备上回答/编辑。
    • 是的,我知道除非我坐在我的电脑前,否则我的努力是不值得的。
    猜你喜欢
    • 2016-02-24
    • 2011-06-06
    • 2010-09-26
    • 1970-01-01
    • 1970-01-01
    • 2018-03-08
    • 2016-04-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多