【问题标题】:Array initialization with variables带变量的数组初始化
【发布时间】:2013-06-19 01:32:36
【问题描述】:

我正在尝试将二维数组传递给函数。 我没有麻烦将它传递给函数。但是我很难理解这背后的逻辑。 函数和主要定义是这样的:

// Function to print the two-dimensional array
void print(int x, int y, int a[x][y]){
    printf("\n");
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++)
            printf("%d     ", a[i][j]);
        printf("\n");
    }
}

// Function to initialize the two-dimensional array
void init_2d(int *a, int x, int y){
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++){
            a[i*y + j] = i + j;
        }
        printf("\n");
    }
}

int main(){
    int m = 2, n = 3;
    int a[m][n];  // a two dimensional whose size has been defined using m and n
    init_2d(a, m, n);
    print(m, n, a);
}

具有讽刺意味的是,一切都运行良好。这是我的问题,因为我无法消化它的逻辑。

主要问题有:

  1. 我在书中读到的是,二维数组的大小应该使用常量或符号常量来定义。在我的主要内容中,我使用变量 mn 定义二维数组,但它工作正常。为什么?
  2. 我还被告知通过将二维数组衰减为一维数组(通过在函数中将其定义为指向 int 的指针)来传递二维数组,即我在函数 init_2d 中所做的方式。但在print 函数中,我使用的是一个二维数组,其大小已使用变量xy 定义。这样做可以吗?
  3. 是否也可以使用指向指针的指针来遍历二维数组?

任何人都可以建议我阅读有关此主题的好书,以清除我的所有概念吗?

我正在使用 codeblocks 来编译我的代码,编译器是 GNU GCC Compiler

【问题讨论】:

  • 2 很奇怪 - 我很惊讶它编译了!
  • @John3136 C99 支持前向参数声明。
  • @Armin:什么是“前向参数声明”,它与 John3136 的评论有何关系?
  • @AndreyT 什么是“前向参数声明”:void print(int x, int y, int a[x][y]) 并连接到 VLA。 gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Variable-Length.html 我从其他地方得到了关键字。

标签: c multidimensional-array


【解决方案1】:
  1. 是和不是。您在书中读到的内容适用于 C 语言规范的较旧的原始版本 - C89/90。由于 C99 版本的 C 语言编译器支持所谓的可变长度数组 (VLA),其大小可以由运行时值指定。您无意中使用了该语言的 C99 特定功能,您的编译器在其默认模式下显然支持该功能。如果您要求编译器切换到严格的 C89/90 模式,您的代码将无法编译,因为您使用了非常量来指定数组大小。

    请注意,即使在 C99 中,VLA 也仅在某些上下文中受支持,例如局部数组和函数参数声明。您将无法声明静态 VLA 或作为结构类型成员的 VLA。在这些情况下,即使在 C99 中,您仍然需要使用常量数组大小。

  2. 告诉你将二维数组衰减为一维数组的人是非常错误的。您在程序中使用init_2d 的方式是无效的:您不能自己将二维数组“重新解释”为一维数组。任何 C 编译器都会立即抱怨你的

    init_2d(a, m, n);
    

    调用,因为此调用尝试为int * 参数传递二维int 数组参数。这是非法的。要使编译器静音,您必须这样做

    init_2d((int *) a, m, n);
    

    您的init_2d 很有可能“按预期工作”,但仍然没有充分的理由使用这样的技巧。

    这里真正可以做的是将二维数组衰减到一个指向一维数组的指针。信不信由你,这正是您在print 声明中所做的,即使它不会立即引起注意。你的

    void print(int x, int y, int a[x][y])
    

    声明实际上等价于

    void print(int x, int y, int (*a)[y])
    

    声明。 IE。 a 实际上是指向类型 int [y] 的指针 - 指向大小为 y 的一维 int 数组的指针。以上两个声明只是说同一件事的两种表面上不同的方式。

    还请注意,不需要通过将二维数组“衰减”到任何东西来将它们传递给函数。您可以通过指向整个 2D 数组的指针传递 2D 数组,如

    void print(int x, int y, int (*a)[x][y])
    

    在这种情况下,要传递数组,您必须使用 &amp; 运算符

    print(m, n, &a);
    

    要访问函数内部的数组,您必须使用 * 运算符

    printf("%d     ", (*a)[i][j]);
    
  3. 不,在你的情况下它不可能。指针对指针无法遍历内置二维数组。顺便说一句,正如您在 init_2d 实现中已经发现的那样(“有效”,即使它是非法的),2D 数组在内部实现为 1D 数组,具有自动 2D 到 1D 索引重新计算。指针对指针不会帮助您遍历这种“平坦”的线性数据。

    在处理完全不同类型的多维数组时,通常会使用指针对指针类型(或更一般地说,多级指针类型):当 N 维数组被实现为指针数组时到独立分配的(N-1)维数组。这种多维数组在 C 程序中也经常使用,但必须手动实现和管理(您可以在 SO 上找到很多示例)。同时,正如我上面所说,内置语言数组以完全不同的方式实现,它们不能被多级指针遍历。

【讨论】:

  • 您的回答很有帮助。谢谢 :) 我对您的回答有些疑问:我可以理解声明为 a[m][n] 的数据结构不能使用二维数组进行遍历。但是假设我声明了一个指向指针 (**a) 的指针并使用 malloc 为其分配适当的空间。那么我可以像二维数组(a[i][j])一样使用它吗?如果它有效,那么它是否也适用于旧版本的 C。
  • 另一个疑问:如果我声明 a[m][n] 那么不是 'a' 指向具有 m x n 大小的数组的指针。那为什么不能像我在'init_2d'函数中那样将二维数组作为指针传递。
  • @Nishant:正如我上面所说,可以通过(**a) 指针手动实现一种特殊的二维数组。但是,不可能简单地为这样的数组分配一个“平面”内存块。通过(**a) 指针实现的数组必须具有两层结构。该结构必须手动构建。而且它将与内置的二维数组完全不同且不兼容。例如,请参见此处:stackoverflow.com/questions/2842135/…
【解决方案2】:
  1. VLA(可变长度数组)存在于某些版本的 C 中。VLA 允许使用运行时分配的变量在堆栈上分配固定长度的数组,而不是通过 malloc() 或类似函数在堆上分配。

  2. 我之前从未见过print() 中使用的数组声明语法,所以我无法回答。通常人们会将括号留空,或者使用衰减的指针。

  3. 是的。

【讨论】:

    【解决方案3】:
    1. ISO C90 禁止变长数组,但是你的编译器比较新,所以没关系。如果您使用 -ansi -pedantic -Wall 标志编译,它将失败
    2. 如果您使用 -ansi -pedantic -Wall 标志编译也会失败(ISO C90 也不喜欢它)。不过,您的新编译器就可以了。
    3. 可以,可以用指针来遍历二维数组(其实main的char** argv参数就是一个例子)

    您正在阅读的书籍看起来符合 C90 标准。不过较新版本的 gcc 没问题。

    【讨论】:

      【解决方案4】:
      1. VLA。
      2. init_2d 接受 int *,但您传递给它的是 int (*)[n]。但是,由于a 是数组的数组,所以a[0] 的地址与a[0][0] 的地址相同,这就是init_2d“有效”的原因。当您将a 传递给init_2d 时,您的编译器应该警告您类型不兼容。如果没有,您应该向您的编译器供应商投诉。

        print 使用 VLA 作为第三个参数。

      3. 如果你的意思是“我可以用int ** 遍历a,答案是“不能直截了当”。原因是如果你有int **p,你只能现实地遍历a,如果p 是这样初始化的:

        int a[n][m];
        int *ap = &a[0][0];
        int **p = &ap;
        

        但是,您只是通过取消引用 p 来遍历 a 通过 ap。指向指针的指针通常用于表示指针数组。

        int *x[10];
        int **p = x;
        

        但是,您的 a 不是指针数组。

      【讨论】:

        【解决方案5】:

        C 数组中的内存分配是连续的,这就是为什么表达式 a[i][j] (init_2d) 可以同化为 a[i*y + j] (print)。

        a as int** (2-dim : N x M) 也可以看作是int* 一维数组(大小:N*M)...

        查看其他人对 1. 2. 和 3 的好答案。

        请参阅:Row-major order (C-style),所以 2. 很好。

        注意: 显然,我在这里所说的仅适用于最新版本的 C 编程语言和/或某些特定上下文,并且绝对不可推广。所以最好先阅读其他几个答案。

        【讨论】:

          【解决方案6】:

          1) 您的 main 中的 m 和 n 已经有一个值,因此您的数组声明很好。它们仅用作数字常量。 2)数组是一个指针。所以

          void foo(int array[]);
          void foo(int* array);
          

          完全一样。

          数组 int a[2][2] 是一个一维数组,有 2 个指向 int 的指针,这 2 个指针指向 2 个整数。因此,在您的第一个指针周围正确播放(在您的情况下为 a),您可以访问这些整数。

          3) 查阅维基百科。有一个很好的算法可以更好地理解。

          【讨论】:

            猜你喜欢
            • 2021-03-04
            • 2014-04-21
            • 1970-01-01
            • 2021-07-08
            • 1970-01-01
            • 1970-01-01
            • 2021-05-07
            • 2011-05-09
            • 1970-01-01
            相关资源
            最近更新 更多