【问题标题】:In 2D array A[m][n], how value of A is same as *A?在二维数组 A[m][n] 中,A 的值如何与 *A 相同?
【发布时间】:2021-04-25 03:21:58
【问题描述】:

我对二维数组的了解:

  1. 在数组中,数组名是指向第一个元素地址的指针
  2. 这里我们可以将 A 视为数组的数组,因此 A 将指向第 0 个一维数组
  3. 所以 A+i 将指向 A 的第 i 个元素
  4. *(A+i) 将指向 A 的第 i 个元素的第一个元素
  5. 那么在二维数组中A+i的地址值应该和*(A+i)一样

但这对我来说没有意义 A 值与 *A 的值如何相同,有人可以告诉我这是如何在内存中工作的吗?我知道这是正确的,但我无法向自己解释

【问题讨论】:

  • A 是如何声明的?
  • int A[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };我不知道这是否有区别
  • 确实如此;这意味着#4是错误的。

标签: c pointers multidimensional-array implicit-conversion pointer-arithmetic


【解决方案1】:

声明

在数组中,数组名是指向第一个元素地址的指针

错了

对于任何数组,其符号本身都会衰减到指向其第一个元素的指针。

所以对于您的数组A,它将衰减到&A[0]

如果你取消引用该指针,就像*A 发生的那样,那么你就有*(&A[0])。这与普通的A[0] 相同。

由于您的数组A 是一个数组数组,那么A[0] 是一个数组,它反过来也会衰减为指向其第一个元素的指针。所以A[0] 会衰减为&A[0][0]

所以*A 将与&A[0][0] 相同。

然而,对于不同的指针,类型有很大的不同。

以您的示例数组为例:

int A[3][4];

那么&A[0] 将是一个指向四个int 值或int (*)[4] 的数组的指针。而&A[0][0] 将是一个指向单个int 值或int * 的指针。


现在为什么所有这些指针看起来都是一样的,这是因为它们都指向同一个位置,这也恰好与数组本身的位置相同(即&A,它将具有类型int (*)[3][4])。

如果我们“绘制”它,它将看起来像这样:

+---------+---------+---------+---------+--------- +---------+-----+ | A[0][0] | A[0][1] | A[0][2] | A[0][3] | A[1][0] | A[1][1] | ... | +---------+---------+---------+---------+--------- +---------+-----+ ^ | &一种 | &A[0] | &A[0][0]

如您所见,所有三个指针都指向同一个位置,但如上所述具有不同的类型。

【讨论】:

    【解决方案2】:

    "在数组中,数组名是指向第一个元素地址的指针。"这不是真的。是时候忘记你曾经听过它了。这部分是正确的,在某些有限的情况下它可能是一个有用的解释,但迟早会比它可能提供的任何有用的解释造成更多的混乱。

    真正的事实是这样的:当你在一个表达式中请求一个数组的“值”时,你得到的是一个指向它的第一个元素的指针。

    所以对于任何数组A,如果你尝试像这样打印它的值:

    printf("%p\n", A);
    

    您将看到一个指向数组第一个元素的指针。

    但是你有一个二维数组。所以如果你要求A,你会得到一个指向数组第一个元素的指针。但是当你请求*A 时会发生什么?

    好吧,如果A 给你一个指向数组第一个元素的指针,那么*A 给你那个指针的“内容”,数组的第一个元素,它是……另一个数组!

    如果您尝试获取“那个”数组的值,请说

    printf("%p\n", *A);
    

    你得到的是一个指向那个数组第一个元素的指针。

    我想你可以看到,对于二维数组,指向整个数组的指针将与指向数组中第一行的指针相同,这与指向第一个元素的指针相同数组的第一行。


    其余部分一开始可能有点令人困惑。我说,“当你在一个表达式中请求一个数组的‘值’时,你得到的是一个指向它的第一个元素的指针。”更正式地说,这意味着在一个表达式中,当您引用 A 时,它与您所说的 &A[0] 100% 完全一致。

    因此,由于* 有点撤销& 的效果,所以当你说*A 时,它100% 完全和你说的A[0] 完全一样。

    上述情况适用于任何类型的数组。对于二维数组,事情会变得更有趣。

    首先,对于二维数组,当我们查看*AA[0] 时,它们中的任何一个都指向另一个 数组——二维数组的第一行。

    所以A 会为您提供指向A 的第一个元素的指针,该元素是另一个数组。
    *A 为您提供指向第一行第一个元素的指针,这是一个实际的单元格。
    所以表达式A*A 将具有相同的指针值,但是它们有不同的类型!
    第一个的类型为“指向任意数组的指针”,而第二个的类型为“指向任意数组的指针”。

    另请参阅旧版C FAQ list 中的question 6.12

    【讨论】:

      【解决方案3】:

      如果您有一个数组,那么它在表达式中使用的指示符(例如用作sizeof 运算符的操作数)会被转换为指向其第一个元素的指针。

      如何为多维数组正确编写这样的指针?

      假设你有一个像这样的多维数组

      T a[N1][N2][N3][N4];
      

      其中T 是某种类型,N1N2N3N4 是子数组中元素的数量。然后要获得指向数组元素类型的指针,您可以重写数组,如

      T ( a[N1] )[N2][N3][N4];
      

      所以要获得指针,只需将记录 a[N1] 替换为记录 *p 就像

      T ( a[N1] )[N2][N3][N4];
      T ( *p )[N2][N3][N4] = a;
      

      现在指针 p 指向数组 a 中类型为 T [N2][N3][N4] 的第一个元素。

      这里有一些例子

      T ( a[N1] );
      T ( *p ) = a; // that can be simplified like T *p = a;
      
      T ( a[N1] )[N2];
      T ( *p )[N2] = a;
      
      T ( a[N1] )[N2][N3];
      T ( *p )[N2][N3] = a;
      

      等等。

      在数组中,数组名是指向第一个元素地址的指针

      C 标准(6.3.2.1 左值、数组和函数指示符)

      3 除非它是 sizeof 运算符的操作数或一元 & 运算符,或者是用于初始化数组的字符串文字, 具有“类型数组”类型的表达式被转换为 类型为“pointer to type”的表达式,指向初始 数组对象的元素并且不是左值。如果数组对象 有注册存储类,行为未定义。

      这里我们可以将 A 视为数组的数组,因此 A 将指向第 0 个 1D 数组

      是的,多维数组是一个数组元素依次是数组。

      所以 A+i 将指向 A 的第 i 个元素

      是的,在表达式A + i 中,数组指示符被转换为指向其第一个元素的指针。因此,使用指针算法,源表达式指向数组的第 i 个元素。

      *(A+i) 将指向 A 的第 i 个元素的第一个元素

      表达式*( A + i ) 产生表达式A + i 指向的对象的左值。如果 A 是一个二维数组,那么表达式 *( A + i ) 会产生一维数组,它位于源数组的第 i 个“行”中。表达式 *( A + i ) 等价于表达式 A[i]

      那么在二维数组中A+i的地址值应该和*(A+i)一样

      A + i 是指向数组 A 的第 i 个元素的 a。如果 A 是一个二维数组,则表达式 *( A + i ) 产生数组的第 i 行,它是一个一维数组.在表达式中使用的一维数组的这个指示符*( A + I ) 又被转换为指向其第一个元素的指针。所以这两个指针A + i*( A + i )在隐式转换后最后一个指针表达式将具有相同的值但不同的类型。

      如果你有

      T A[N1][N2];
      

      那么表达式A + i 的类型为T( * )[N2]。解引用 *( A + i ) 之类的表达式,您将获得原始数组的第 i 个元素,它是 T[N2] 类型的一维数组。反过来,表达式中使用的这个数组指示符被转换为其类型 T * 的第一个元素,并且两个指针 T( * )[N2]T * 将在原始数组占用的内存范围内具有相同的地址

      这是一个演示程序。

      #include <stdio.h>
      
      int main(void) 
      {
          enum { N1 = 3, N2 = 5 };
          int ( a[N1] )[N2];
          int ( * p )[N2] = a;
          
          for ( size_t i = 0; i < N1; i++)
          {
              printf( "a + %zu = %p, p + %zu = %p\n", 
                      i, ( void * )( a + i ), i, ( void * )( p + i ) );
              printf( "*( a + %zu ) = %p, *(p + %zu ) = %p\n\n", 
                      i, ( void * )*( a + i ), i, ( void * )*( p + i ) );
          }
          
          return 0;
      }
      

      它的输出可能看起来像

      a + 0 = 0x7ffda1063ab0, p + 0 = 0x7ffda1063ab0
      *( a + 0 ) = 0x7ffda1063ab0, *(p + 0 ) = 0x7ffda1063ab0
      
      a + 1 = 0x7ffda1063ac4, p + 1 = 0x7ffda1063ac4
      *( a + 1 ) = 0x7ffda1063ac4, *(p + 1 ) = 0x7ffda1063ac4
      
      a + 2 = 0x7ffda1063ad8, p + 2 = 0x7ffda1063ad8
      *( a + 2 ) = 0x7ffda1063ad8, *(p + 2 ) = 0x7ffda1063ad8
      

      注意a + 0表达式的值小于20表达式a + 1的值(或0x14的十六进制值)因为sizeof( *( a + i ) )等于sizeof( int[5] )等于20

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-24
        • 1970-01-01
        • 2018-09-30
        • 2016-11-14
        • 1970-01-01
        • 2015-08-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多