【问题标题】:Creating and returning multi-dimensional arrays创建和返回多维数组
【发布时间】:2021-01-05 16:10:23
【问题描述】:

我阅读了一些动态创建和使用二维数组的方法,我已经确定了这种方式:

文件:

5 4
+---+
|xxx|
|xxx|
+---+

main.c:

char** loadArray() {
    FILE *in = fopen("file", "r");

    int w, h;
    fscanf(in, "%d %d\n", &w &h);

    char (*buf)[w] = malloc(sizeof(char[h][w]));

    for (int i = 0; i < h; i++) {
        fscanf(in, "%s\n", buf[i]);
    }

    fclose();

    return buf;
}

int main() {
    char** array = loadArray();
    for (int i = 0; i < 4; i++) { // magic number, only because I know the size
        printf("%s\n", array[i]);
    }
    return 0;
}

虽然它会编译,但它会发出警告:incompatible pointer types returning 'char (*)[w]' from a function with result type 'char **',如果我尝试运行它会出现段错误。

几个问题(主要针对 C,但欢迎 C++ 特定答案以供将来参考,当我到达那里时):

  1. 多维数组的正确返回类型是什么?我认为该警告不是很有帮助,因为它提供了一个可变术语,我显然在阅读文件之前没有。
  2. 此时,我只是想让返回的二维数组正常工作,但当我这样做并继续前进时,我将需要数组的维度以便以后正确使用。我的第一个想法是返回结构,我可以在其中保存维度和数组本身。然而,经过进一步思考,可变大小让我认为我无法将单个结构模板用作函数的返回类型,我必须找到其他方法来获取数组的大小.是想法吗?

感谢您的宝贵时间。

【问题讨论】:

  • 通常的答案:使用std::vector。那是一个动态的对象数组,它知道自己的大小,其元素本身可以是vectors,等等。
  • C 和 C++ 在这类问题上非常不同。您的示例代码让我认为您只是在使用 C 构造?如果是这样,请删除 C++ 标签。
  • 我明白了,感谢您对C++方面的建议,但我仍在寻找C解决方案。
  • malloc 是否有足够的空间容纳 C 字符串?请记住,字符串末尾必须包含zero
  • 最后没有多余的空字节,我似乎做得很好,因为我只是想“代表 80x24 显示器的一部分”。不要乱搞字符串本身,我只需要能够打印出来。这真的是一个问题吗?我最初想一次扫描一个角色,但这对我来说听起来不是一个好主意。

标签: arrays c multidimensional-array


【解决方案1】:

指向指针的指针和指向数组的指针是完全不同的东西。您不能在返回类型中返回数组维度,因此您必须使用不完整的数组:

char (*(loadArray()))[] {
  ...
}

显然这不是很可读所以你最好通过typedef

typedef char line[];

line* loadArray() {
  ...
}

您还必须掌握维度wh,因此您必须将指向值的指针作为参数传递。但这将是另一个问题。

【讨论】:

    【解决方案2】:

    多维数组的正确返回类型是什么?

    没有“正确”的类型,这完全取决于想要如何表示数据以及想要设计什么样的架构。

    我认为警告不是很有帮助,因为它提供了一个可变术语

    该警告非常有用,因为它表明代码中存在错误——您正在将指向数组的指针转换为指向指针的指针。忽略该警告最终会导致 printf("%s\n", array[i]); 这是未定义的行为。

    是想法吗?

    您可以返回一个指向动态分配的指针数组的指针,该指针指向动态分配的值数组。

    char **loadArray(void) {
        size_t w, h;
        // Initialize w and h.
    
        char **arr;
        arr = malloc(w * sizeof(*arr));
        if (!arr) return NULL;
        for (size_t i = 0; i < w; ++i) {
           arr[i] = malloc(h * sizeof(*arr[i]));
           if (!arr[i]) {
              for (size_t j = 0; j < i; ++j) {
                 free(arr[j]);
              }
              frer(arr);
              return NULL;
           }
       }
    
       // Fill arr from file. Handle errors. Handle deallocation.
    
       return arr;
    }
    

    这是用 C 编程的常用方法,人们会期望 char ** 以这种方式行事。我可以举一个 POSIX 的例子,其中 scandir 函数接受一个指向双指针 struct dirent ***namelist 参数的指针,该参数分配给这样一个动态分配的二维数组。

    但让我们采用过度设计(或不过度设计)的设计(主要是 C-ish 伪代码):

    // represents the thing
    struct thething_s {
        char *string;
    };
    int thething_print(struct thething_s *t, FILE *f) {
       return fprintf(f, ....);
    }
    void thething_free(struct thething_s *t) {
       free(t->string);
    }
    int thething_load_from_file(struct thething_s *t, FILE *f, size_t len) {
       t->string = malloc(len);
       if (!t->string) return -1;
       if (scanf(....) != 1) return -2;
       return 0;
    }
    
    // represents an array of things
    struct thingsarr_s {
        struct thethings_s *things;
        size_t w;
        size_t h;
    };
    int thigsarr_load_from_file(struct thingsarr_s *t, FILE *f) {
        // Initialize t->w and t->h.
        t->things = malloc(t->w * sizeof(*t->things));
        if (!arr) return -1;
        for (size_t i = 0; i < w; ++i) {
           if (thething_load_from_file(&t->things[i], f, t->h) != 0) {
              for (size_t j = 0; j < i; ++j) {
                 thethings_free(t->things[j]);
              }
              free(t->things);
              return -1000 - i;
           }
       }
       return 0;
    }
    const struct thething_s *thingsarr_get_thing(const struct thingsarr_s *t, size_t idx)
    {
       assert(idx < t->len);
       return t->things[idx];
    }
    struct thething_s *thingsarr_get_thing_nonconst(struct thingsarr_s *t, size_t idx) {
       return (struct thething_s *)thingsarr_get_thing(t, idx);
    }
    void thingsarr_free(struct thingsarr_s *t) {
      for (.,.) {
         thething_free(&t->things[i]);
      }
      free(t->things);
    }
    

    哪个是“最好的”很大程度上取决于上下文、特定需求和特定应用。

    【讨论】:

      【解决方案3】:

      指针类型char **char (*)[w] 非常不同。有一个普遍的误解,认为数组和指针在 C 中是同一个东西,但事实并非如此。

      您希望函数的返回类型为char (*)[w],但该返回类型无法声明,因为w 是未知值。此外,调用者不知道返回数组的宽度和高度,因为该信息是 loadArray 函数的内部信息。

      loadArray 函数的返回类型设置为void* 允许它在不知道宽度或高度的情况下只返回指向数组的指针。可以使用指针参数将宽度和高度值发送给调用者,以便调用者可以重构数组类型。

      对原始 C 代码的以下修改实现了上述更改。 (据我所知,C++ 不支持变长数组类型,所以这只是 C。)

      #include <stdio.h>
      #include <stdlib.h>
      
      void* loadArray(int *pw, int *ph) {
          FILE *in = fopen("file", "r");
      
          int w, h;
          fscanf(in, "%d %d\n", &w, &h);
          *pw = w;
          *ph = h;
          char (*buf)[w] = malloc(sizeof(char[h][w]));
      
          for (int i = 0; i < h; i++) {
              fscanf(in, "%s\n", buf[i]);
          }
      
          fclose(in);
      
          return buf;
      }
      
      int main(void) {
          int w, h;
          void* larray = loadArray(&w, &h);
          char(*array)[w] = larray;
          for (int i = 0; i < h; i++) {
              printf("%s\n", array[i]);
          }
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-01-20
        • 1970-01-01
        • 1970-01-01
        • 2011-08-19
        • 2013-04-21
        相关资源
        最近更新 更多