【问题标题】:How to make multi-dimensional arrays less prone to cause insanity?如何使多维数组不易引起精神错乱?
【发布时间】:2015-02-19 11:40:41
【问题描述】:

我正在学习 88 个 C 程序,在将近 25 年后重新学习 C(由于自 1990 年以来该语言本身至少有两次主要版本修订,这一事实变得复杂,而且我确信我当时使用的 Turbo-C 不完全兼容 C89)。我不记得我参加的课程对二维数组做了任何重要的事情,并将它们传递给函数,并期望我需要在编译时不知道维度的情况下处理数据,这很好地制作我的头爆炸了。

我正在使用 gcc(目前在 Ubuntu 14.04 的标准存储库中找到的版本),我收集的它应该支持 C99 或 C2011 标准下的可变长度数组声明,但我试图使用的声明使函数识别数组而不必在编译时知道大小会收到有关“冲突类型”的错误。我正在编译时将警告设置为 max,使用一个小型 Python 程序来节省每次需要编译时都必须键入一个长命令行(结果命令是 gcc -std=c99 -pedantic -Wall -Wextra -o target target.c -lm)。以下是原型、调用者行和函数定义(代码也充满了引起警告的不良做法,我从书中复制了它;我现在不关心这些,我有兴趣学习理智的方法来做到这一点,所以我不必使用那些丑陋、令人困惑的方法)。

#include <stdio.h>
#include <stdlib.h>

/* ANSI prototypes */

void s2 (int, int, int*);
int main (void);

int main (void)
{
  int one[5]={0,1,2,3,4};
  int two[3][4] =
  {
{0,1,2,3},
{10,11,12,13},
{100,101,102,103}
  };

  /* try to call s2, it won't work */
  printf("\ncalling s2 \n");
  s2(1, 5, one);
  s2(3, 4, two); /* will be compiler warning */
}

void s2(int rows, int cols, int *x[rows][cols])
{
  /* wants to use multiple square brackets; needs dimension info */
  int i,j;
  for (i=0; i<rows; i++)
  {
     for (j=0; j<cols; j++)
       printf("%4i", x[i][j]);
  }
}

这会给出以下警告和错误字符串:

./gccm sotest
sotest.c: In function ‘main’:
sotest.c:22:3: warning: passing argument 3 of ‘s2’ from incompatible pointer type [enabled by default]
  s2(3, 4, two); /* will be compiler warning */
  ^
sotest.c:6:6: note: expected ‘int *’ but argument is of type ‘int (*)[4]’
void s2 (int, int, int*);
  ^
sotest.c: At top level:
sotest.c:25:6: error: conflicting types for ‘s2’
void s2(int rows, int cols, int *x[rows][cols])
  ^
sotest.c:6:6: note: previous declaration of ‘s2’ was here
void s2 (int, int, int*);
  ^
sotest.c: In function ‘s2’:
sotest.c:32:7: warning: format ‘%i’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
  printf("%4i", x[i][j]);
  ^

我至少花了几个小时在这里挖掘其他类似的问题,阅读网络上的文章,并且通常试图将我的大脑包裹在当你需要通过二维数组到 C 中的函数——在许多其他语言中非常简单,而一维数组在 C 中很容易!我没有发现任何有意义的东西;我见过的唯一明智的建议是将数组包装在Struct 中,但是在 C 被广泛使用的前二十年左右肯定不存在这些建议,我不确定它是如何/为什么会这样的更好。

【问题讨论】:

  • 你看这里无数类似的问题了吗?
  • @2501 他们无数的事实使得在有限的时间内不可能查看所有这些,但我确实检查过的样本(前 2-3 个搜索页面上的那些标题看起来相关) 没有回答我的问题——尽管完整阅读了其中的几个。
  • struct 从一开始就是语言的一部分 (K&R)。没有解决“如何/为什么让它变得更好”,只是必须向使用网络上各种真正古老但免费的编译器之一的人指出这一点。
  • 后期跟进——使用struct 让一切大大变得更简单;在大多数情况下,结构就像一维数组一样传递(并且很容易从编译错误中找出需要指针的少数情况)。对可变长度数组没有多大作用,但那里似乎没有太多可用的简化。

标签: c arrays c99


【解决方案1】:

您的声明应如下所示:

void s2(int rows, int cols, int x[rows][cols]);

该函数采用二维整数数组。 使用该声明,您可以使用变量 2 进行调用。您对变量一的调用是错误的,因为一只有一维。

另外,请确保您的声明和函数匹配,然后您的函数需要如下所示:

void s2(int rows, int cols, int x[rows][cols]) {

【讨论】:

  • 也可以和void s2 (int, int, int[][*]);一起使用
  • 啊哈!我通常不需要/不希望我的原型那么具体。让我试试……
  • @RyanHaining——就让我的大脑受伤而言,这和我试图不使用的那些一样糟糕......
  • [*] 表示它将由当前不可用的东西决定,因为除非您决定命名它们,否则您不会想要命名参数,这将是我的建议
  • @RyanHaining 我对在我的原型中命名参数没有任何反对意见,我刚刚学习了没有名称的原型,并且它们以这种方式工作得很好,直到我开始处理一维以上的数组。跨度>
【解决方案2】:

这是使用新 VLA 功能的完整代码。唯一的技巧是您需要传递数组one 的地址。由于one 是一维数组,one 的地址是一个行指针,即它指向一个行数组(尽管是一个只有一行的数组)。数组two 已经是一个行指针数组,因为数组two 是一个二维数组。

是的,我知道,这不是一个很好的解释。如果您有 K&R 第 2 版的旧副本,行指针将在 5.7 和 5.9 节中(不好地)解释。

#include <stdio.h>
#include <stdlib.h>

void s2( int rows, int cols, int array[rows][cols] );

int main (void)
{
    int one[5]={0,1,2,3,4};
    int two[3][4] =
    {
        {0,1,2,3},
        {10,11,12,13},
        {100,101,102,103}
    };

    printf( "array one\n" );
    s2( 1, 5, &one );
    printf( "array two\n" );
    s2( 3, 4, two );
}

void s2( int rows, int cols, int array[rows][cols] )
{
    int i, j;

    for ( i = 0; i < rows; i++ )
    {
        for ( j = 0; j < cols; j++ )
            printf( " %3i", array[i][j] );
        printf( "\n" );
    }
}

【讨论】:

  • 这种解释至少与我发现的不完全重复问题的答案中的任何解释一样有意义。当然,其中许多答案坚持认为二维数组不是指针数组,但其中一些人承认我们有时可以假装它是。我还发现令人鼓舞的是,您上面的代码与我通过实现 Henrik 的答案得到的代码基本相同(并修复了阻止 s2 遍历列的杂散分号)。
  • @ZeissIkon 是的,困难在于 C 处理指针和数组的方式。数组衰减为指针(无论这意味着什么),并且指针可以与数组语法一起使用。对于一维数组,这还不错,因为语法相当干净。但是二维数组很难解释,因为声明行指针的语法是一团糟,行指针的指针数学超出了日常经验的范围。因此,这是我知道如何使用它的事情之一,但我有一段时间试图解释它。
  • 一维数组在传递给函数时从 int array[x] 衰减到 int *array二维数组int array[x][y]衰减到int (*array)[y]
【解决方案3】:

虽然不是最佳解决方案,但您可以依靠数组按顺序存储在内存中这一事实,将多维矩阵视为一维数组。这是可能的,因为您已经将数组大小传递给函数,这样可以轻松计算数组中每个元素的address offset。 (不管有多少维度)。它需要在调用中进行强制转换:

#include <stdio.h>
#include <stdlib.h>

/* ANSI prototypes */

void s2 (int, int, int *);
int main (void);

int main (void)
{
    int one[] = { 0, 1, 2, 3, 4 };
    int two[][4] = {
        {0, 1, 2, 3},
        {10, 11, 12, 13},
        {100, 101, 102, 103}
    };

    printf ("\ncalling s2 for 'one'\n");
    s2 (1, 5, (int *)one);

    printf ("\ncalling s2 for 'two'\n");
    s2 (3, 4, (int *)two);      /* will be compiler warning - fixed */

return 0;
}

void s2 (int rows, int cols, int *x)
{
    /* wants to use multiple square brackets; needs dimension info */
    int i, j;
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++)
            printf ("%4i", x[i * cols + j]);
        printf ("\n");
    }
}

输出:

$ ./bin/multidimasz

calling s2 for 'one'
   0   1   2   3   4

calling s2 for 'two'
   0   1   2   3
  10  11  12  13
 100 101 102 103

还需要删除第二个for 循环:) 后面的额外;

【讨论】:

  • 据我了解,连续内存分配要么取决于实现,要么完全未定义; Henrik 在下面的回答提供了一个我更满意的解决方案(我确实在您的回答上升前一分钟发现了额外的分号)。
  • 我同意这就是我使用this is not an optimal solution 限定答案的原因。重新学习时看起来不错。
  • @ZeissIkon C99 faq and Variable Length Arrays 上有一个很好的常见问题解答。它可以填补讨论的微妙之处。
  • @DavidCRankin 谢谢,我会看看的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多