【问题标题】:The name of a 2-D array in CC中二维数组的名称
【发布时间】:2019-02-25 03:29:52
【问题描述】:

我正在学习 C 中的指针和数组,这是一个让我困惑了一段时间的问题:

因此,一维 int 数组的名称是一个指向 int 的常量指针,它指向该数组中的第一个元素。因此,当我们评估一维数组的名称时,我们应该得到数组中第一个元素的地址。

对于 2D int 数组,数组名称是指向 int 的第一个数组的指针。那么 2D int 数组名称的值是多少?我认为它应该是这个二维数组中第一个数组的地址。但是 C 中如何定义数组的地址呢?它只是该数组中第一个元素的地址吗?

【问题讨论】:

  • C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)。数组不是指针,指针也不是数组,但在访问时,数组会转换为指向第一个元素的指针,该元素会受到链接中列出的 4 个异常的影响。
  • 对不起,我想我在这里不够清楚。我实际上明白数组不是指针。在我的问题中,我的实际意思是,当在表达式中使用数组的名称时,编译器会生成指针常量。
  • 人们可能会相信名字是一个指针,就像函数的名字是一个指针一样。例如,您不能像将函数名称传递给 qsort() 一样将其作为指针参数传递?

标签: c arrays pointers


【解决方案1】:

所以一维 int 数组的名称是一个指向 int 的常量指针

这是错误的,而且经常被教导得很糟糕。数组就是数组。下面是一些类比代码:

int x = 5;
double d = x + 1.2;

在第二行中,x转换为 double 以进行添加。这不会改变x,它仍然是int,转换的结果是“临时的”并且只存在直到添加完成。 + 运算符的条件要求进行转换,即必须将两个算术操作数都带入一个公共类型(在本例中为 double)。

在数组的情况下,假设我们有 char *p = arrayname + 1 ,那么 arrayname 仍然是一个数组。但是它被转换为一个临时指针,以便可以进行加法(+ 运算符需要这样做,它可以添加一个指针和一个整数)。临时指针指向数组的第一个元素,但是说临时指针就是数组是不正确的。

大多数运算符都会调用这种将数组转换为临时指针的方法,但有些则不会。所以说arrayname 是一个指针是不正确的,因为它可能与不将数组转换为指针的运算符一起使用,例如sizeof arrayname

将数组转换为指针的结果是指向该数组第一个元素的指针。即使第一个元素本身就是一个数组,这始终是正确的。

但是在 C 中如何定义数组的地址呢?它只是该数组中第一个元素的地址吗?

没有。每个变量都有一个地址,这适用于数组和非数组。如果您了解int 的地址,那么您也了解char 的2x2 数组的地址。

【讨论】:

    【解决方案2】:

    让我们澄清一些事情:

    int a = 24;
    

    以上内容很多:

    • 声明:我们声明了一个名为 a 的变量,类型为 int
    • 定义:创建int 类型的对象。
    • 一个初始化:这个对象被初始化为24

    让我们回顾一下:int 类型的对象是用值24 和变量a 创建的命名它。

    现在让我们将其应用于以下内容:

    int a1[3] = {0, 1, 2};
    
    • 声明:我们声明了一个名为 a1 的变量,类型为 int[3](也就是 3 个整数的数组)。
    • 定义:创建“3 个整数数组”类型的对象
    • 一个初始化:用{0, 1, 2}初始化对象

    变量a1 为该对象命名。

    所以一维 int 数组的名称是一个指向 int 的常量指针,它 指向该数组中的第一个元素。

    错了。我知道您可能已被告知或阅读过此内容,但这是不正确的。数组不是指针!!数组和指针是不同的类型。话虽如此,出于方便和历史原因,在大多数情况下(但不是全部!),数组会衰减为指向第一个元素的指针:

    int a1[3] = {0, 1, 2};
    int* p = a1; // here a1 decays to a pointer to its first element
    

    上面sn -p中p指向数组a1的元素0

    您可以用同样的方式查看 2D 或 3D 或 nD 数组:

    T a2[3] = {l0, l1, l2};
    

    假设T 是一种类型。以上是“3个Ts的数组”。

    • 如果 Tint 那么我们有 int a2[3] = {0, 1, 2} - 3 个整数的数组。我们称之为一维 int 数组。

    • 但是如果Tint[2],那么上面的就变成了int a2[3][2] = {{00, 01}, {10, 11}, {20, 21}}——你可以把它看成“3 个Ts 的数组”或者“3 个int[2] 的数组”或者“由 2 个整数组成的 3 个数组组成的数组”。

    我们可以应用相同的衰减规则:

    int a2[3][2] = {{00, 01}, {10, 11}, {20, 21}};
    
    int (*p2)[2] = a2; // a2 decays to a pointer to its first element.
                       // Its first element is an array of 2 int.
                       // So a2 decays to `int (*)[2]` -  a pointer to an array of two elements.
    
    

    上面a2指向数组的元素{00, 01}

    【讨论】:

    • 谢谢,你的解释对我来说很有意义!
    【解决方案3】:

    数组名不是指针。在大多数情况下,当使用数组的名称时,它会隐式*转换*为指向其第一个元素的指针,据说,数组衰减为指针。

    当数组的名称是地址运算符 (&)、sizeof 运算符的参数以及字符串字面量(由某个字符组成的数组)时,数组的名称不会衰减为指针type) 用于初始化数组 *)。

    也就是说,一个二维数组

    T arr[COLS][ROWS];
    

    第一个元素是T[ROWS] 类型的数组。所以arr 衰减为T(*)[ROWS] 类型的指针,它指向arr 的第一个元素。


    *) 如果您可能想添加该数组,当它们是 _Alignof-operator 的操作数或在其他地方读取时,它们也不会衰减:

    @EricPostpischi:数组不能是_Alignof 的操作数。在数组转换的异常中包含 _Alignof 是 C 2011 标准中的错误。 _Alignof 操作数只能是类型,不能是表达式。

    【讨论】:

      【解决方案4】:

      当一个二维数组衰减到一个指针时,你就有了一个指向数组的指针。下面是一个示例:

      int arr[5][6];
      int (*p)[6] = arr;
      

      【讨论】:

        【解决方案5】:

        数组不是指针。数组的名称在表达式中使用时“衰减”为指向第一个元素的指针。

        严格来说,C只有一维数组,而二维数组实际上只是数组的数组。

        一维数组:

        • int arr [x] 的第一个元素是int
        • 在表达式中使用arr 时,您将获得指向该元素的指针int*
        • 在对该指针进行指针运算时,每个项目的第一个元素的大小 = sizeof(arr[0])

        二维数组:

        • int arr [x][y] 的第一个元素是int [y]
        • 在表达式中使用arr 时,您将获得指向该元素的指针int (*)[y]
        • 在对该指针进行指针运算时,每个项目的第一个元素的大小 = sizeof(arr[0])

        所以这是相同的规则。 int(*)[y] 数组指针 遵循与普通指针相同的指针运算规则。但是您可以进一步取消引用它以在数组数组中获取单个 int

        【讨论】:

          【解决方案6】:

          “那么 2D int 数组名称的值是多少?”

          “我其实明白数组不是指针。在我的问题中, 我的实际意思是当数组的名称用于 表达式,编译器会生成指针常量。”

          你必须在这里小心。作为您在问题下方评论的后续内容,数组/指针转换规则如何应用影响转换产生的type 存在细微差别。这将决定您是否以及如何在表达式中使用数组名称。

          "...编译器会生成指针常量。"

          否。编译器不生成指针常量,编译器遵循C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)。当在表达式中使用数组名称时,表达式会使用数组转换为指针所产生的地址进行计算(受第 3 段中所述的 4 个例外的影响)。

          关于数组/指针转换的规则不依赖于维数,无论如何该规则都适用。 然而,转换产生的指针的type 确实取决于数组维数。这很关键,这将决定您对数组名称的使用是否有效。

          帮助巩固转化过程中发生的事情的一种方法是逐步进行。从一维数组开始,然后逐步向上。

          6.3.2.1 - 访问时将一维数组转换为指针

          当你有一个简单的数组时,例如

          int array[10];
          

          在访问数组时,转换为指向第一个元素的指针,例如,地址元素&array[0]。 (这只是一个指向 int 的指针,或者具有正式类型int *

          6.3.2.1 - 访问时将二维数组转换为指针

          对于 2D 数组,规则同样适用,例如

          int array[10][10];
          

          这里的array,一个二维数组,本质上是一个包含 10 个 - int[10] 数组(一维数组的数组)的数组。在访问时array[10][10] 以完全相同的方式转换为指向10-int 的第一个数组的指针&array[0][0](这导致指向int[10] 的数组的指针或正式type int (*)[10]) 它不是 pointer-to-pointer(例如 int**),它是一个非常具体的指向 int[10] 数组的指针。

          (noteint *[10] (an array of 10 pointers 之间的重要区别,它在访问时会变成一个指向指针的指针) 和int (*)[10](一个指针 指向10个数组 int))

          回答

          “所以...当在表达式中使用时,2D int 数组名称的值” -- 是第一个 1D array 整数的地址使用正式类型int (*)[N](其中N 是每行的元素数)向上扩展二维数组。

          标准应用方式的细微差别

          类型对于正确使用数组名称至关重要。对于 2D 数组,结果地址是一个指向 array 的指针。如果取消引用该指针会产生什么结果? (答案: 一个 array)当您通过取消引用的指针访问该 array 时会发生什么? (提示:访问规则的转换再次适用)。您必须知道转换后的指针类型是什么,才能在表达式中正确使用数组名称。

          一个例子可能会有所帮助

          或者它可能不会,但是使用由数组访问和指针转换产生的指针类型可能有助于理解。下面的示例声明了一个简单的4x3 2D 数组int。然后它声明一个适当类型的指针 (p) 以允许在将数组地址分配给指针的表达式中使用数组名称。然后使用数组名初始化的指针进一步初始化指向第一个数组中第一个元素的整数指针 (ip)。

          然后该示例输出每个元素的地址,然后使用指针p 输出构成二维数组的每个行数组的开头地址。最后,代码进入验证循环,通过 (1) 数组索引、(2) 使用偏移量的指针 p 保存的地址和 (3) ip 保存的地址来比较每个元素的地址。目的是使用由分配数组名称的表达式产生的每个不同指针,然后引用每个元素并确保每个指针持有的地址一致。

          #include <stdio.h>
          
          int main (void) {
              
              int array[ ][3] = { {1, 2, 3},      /* 2D array values */
                                  {3, 4, 5},
                                  {5, 6, 7},
                                  {7, 8, 9} },
                  (*p)[3] = array,                /* pointer to array */
                  *ip = *p;                       /* integer poiner */
              size_t  size = sizeof array,
                      nele = size / sizeof **array,
                      nrow = size / sizeof *array,
                      ncol = sizeof *array / sizeof **array;
              
              printf ("2D array statistics:\n\n"
                      "  size: %zu  (bytes)\n  nele: %zu  (ints)\n"
                      "  nrow: %zu\n  ncol: %zu\n",
                      size, nele, nrow, ncol);
              
              puts ("\naddress of each array element:\n");
              for (size_t i = 0; i < nrow; i++) {
                  for (size_t j = 0; j < ncol; j++)
                      printf ("  %p", (void*)&array[i][j]);
                  putchar ('\n');
              }
              
              puts ("\naddress of each 1D array:\n");
              for (size_t i = 0; i < nrow; i++)
                  printf ("  %p\n", (void*)p[i]);
              
              puts ("\nvalidating each array element address by index & pointer:\n");
              for (size_t i = 0; i < nrow; i++) {
                  for (size_t j = 0; j < ncol; j++) {
                      if (ip != &array[i][j] || ip != *p + j) {
                          fprintf (stderr, "address validation failed for "
                                  "array[%zu][%zu]\n(%p != %p || %p != %p)\n",
                                  i, j, (void*)ip, (void*)&array[i][j],
                                  (void*)ip, (void*)(p + j));
                          return 1;
                      }
                      ip++;
                  }
                  p++;
              }
              puts ("  done!");
          
              return 0;
          }
          

          使用/输出示例

          $ ./bin/array_2d_access
          2D array statistics:
          
            size: 48  (bytes)
            nele: 12  (ints)
            nrow: 4
            ncol: 3
          
          address of each array element:
          
            0x7ffe7c9a9780  0x7ffe7c9a9784  0x7ffe7c9a9788
            0x7ffe7c9a978c  0x7ffe7c9a9790  0x7ffe7c9a9794
            0x7ffe7c9a9798  0x7ffe7c9a979c  0x7ffe7c9a97a0
            0x7ffe7c9a97a4  0x7ffe7c9a97a8  0x7ffe7c9a97ac
          
          address of each 1D array:
          
            0x7ffe7c9a9780
            0x7ffe7c9a978c
            0x7ffe7c9a9798
            0x7ffe7c9a97a4
          
          validating each array element address by index & pointer:
          
            done!
          

          让我知道这是否有帮助以及您是否还有其他问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-10-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多