如何在C语言中使用指针表达式访问二维数组的元素?

39

我知道对于一维数组, x=a[i] 等同于 x=*(a+i),但是如何使用指针访问二维数组的元素呢?


1
你理解多维数组在内存中的布局吗?你能访问a[0][0]吗?a[0][1]a[0][ARY_N_Y]a[1][0] - dmckee --- ex-moderator kitten
2
C语言实际上没有二维数组。 - Russell Borogove
7
这种说法取决于“一个数组的数组不等同于一个二维数组”的断言,这总是归结于一些挂坠关于内存块如何成为二维数组的声明。无论如何,int a[5][5];看起来和表现得像一个二维数组。至少在它退化为指针之前是这样的。 - dmckee --- ex-moderator kitten
6
应该写作“pedants”,而不是“pendants”。 - Russell Borogove
2
应该在e上加上重音符号:touché。在Mac上,您可以使用Alt-E(也称为option-E)和E来获取该字符。 - Russell Borogove
6个回答

54

摘要: 如果您定义了一个多维数组为 int [][],那么 x = y[a][b] 相当于 x = *((int *)y + a * NUMBER_OF_COLUMNS + b);


无聊的细节:

上面的 (int *) 强制类型转换需要一些解释,因为它的必要性可能不是一开始就直观的。为了理解为什么必须这样做,请考虑以下内容:

  1. C/C++ 中的类型指针算术运算始终将类型指针值 (即地址) 根据字节调整为类型大小,以便通过标量进行加/减/递增/递减。

  2. 多维数组声明的基本类型(不是元素类型;而是变量类型)是比最终维度少一维的数组类型。

后者 (#2) 需要一个示例来巩固。在下面的示例中,变量 ar1ar2 是等价的声明。

int ar1[5][5]; // an array of 5 rows of 5 ints.

typedef int Int5Array[5];  // type is an array of 5 ints
Int5Array ar2[5];          // an array of 5 Int5Arrays.

现在是指针算术部分。就像类型化结构体指针可以按字节大小前进一样,完整的数组维度也可以跳过。如果您将多维数组视为我上面声明的ar2,那么这更容易理解:

int (*arptr)[5] = ar1; // first row, address of ar1[0][0].
++arptr;               // second row, address of ar[1][0].

使用裸指针,所有这些问题都会消失:

int *ptr = ar1; // first row, address of ar1[0][0].
++ptr;          // first row, address of ar1[0][1].
因此,在进行二维数组指针算术运算时,以下方法无法获得多维数组中[2][2]的元素:
#define NUMBER_OF_COLUMNS   5
int y[5][NUMBER_OF_COLUMNS];
int x = *(y + 2 * NUMBER_OF_COLUMNS + 2); // WRONG
理由很明显,因为你要记住y本质上是一个数组的数组。将标量(2*5 + 2)加到y上的指针算术将会增加12行,因此计算出等价于&(y[12])的地址,这显然是错误的,实际上会在编译时抛出一个大警告,或者干脆无法编译通过。通过(int*)y的转型避免了这个问题,因此表达式的结果类型基于裸指向int的指针。
#define NUMBER_OF_COLUMNS   5
int y[5][NUMBER_OF_COLUMNS];
int x = *((int *)y + 2 * NUMBER_OF_COLUMNS + 2); // Right!

@WhozCraig 那就应该是ROW_SIZE,不是吗?无论如何已经更改了。 - antonijn
我尝试通过像这样打印二维数组的一个元素来测试它 printf("%d",*(a + 2 * 3 + 3));。它应该已经打印出了行宽为3的矩阵中a[2][3]的元素。在我的情况下,元素a[2][3]是6,但是会打印0。我做错了什么吗? - Tudor Ciotlos
@TudorCiotlos “我做错了什么吗?” 我怎么知道?你没有提供源代码(顺便在另一个问题中提供)。 - antonijn
@TudorCiotlos 可能不是这样的,这里的指针算术不正确。正确的算术在另一个答案中,只需忽略那里的最后一句话,二维数组与int **非常不同。 - Daniel Fischer
编辑一下,明确地说是 int **。 - antonijn
显示剩余6条评论

25

这个表格

C语言中的2D数组是一系列连续的行(不像Pascal语言)。
当我们创建一个4行5列的整数表格时: A 5*4 integer table.

访问元素

我们可以使用以下方法来访问元素:

int element = table[row-1][column-1];

但我们也可以使用以下代码来实现:

int element = *(*(table+row-1)+column-1);

在这些示例中,rowcolumn是从1开始计算的,这就是为什么要减去1的原因。
在下面的代码中,您可以测试两种技术都是正确的。在这种情况下,我们从0开始计算行和列。

示例

#include <stdio.h>
#include <stdlib.h>
#define HEIGHT 4
#define WIDTH 5

int main()
{
    int table[HEIGHT][WIDTH] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
    int row = 2;
    int column = 2;
    int a = *(*(table+row)+column);
    printf("%d\n",a);//13
    printf("%d\n",table[row][column]);//13
    return 0;
}

解释

这是一个双指针算法,所以table指向第一行,而*table指向第一个元素,如果您对其进行解引用,则**table将返回第一个元素的值。在下面的示例中,您可以看到*tabletable指向相同的内存地址。

printf("%d\n",table);//2293476
printf("%d\n",*table);//2293476
printf("%d\n",**table);//1

在内存中,表格的所有行都是相互跟随的。因为 table 指向第一行,如果我们加上所需元素在表格中的行数,我们将得到一个指向该行的指针。在这种情况下,*(table+row) 将包含给定行的第一个元素的地址。现在我们只需添加列数,例如 *(table+row)+column,即可获取给定行和列中元素的地址。如果我们解引用它,就可以得到该元素的确切值。
因此,如果我们从零开始计算行和列,就可以像这样从表格中获取元素:

int element = *(*(table+row)+column);

在内存中

内存中的表格。


我喜欢你的图形,但是你必须提到表格应该像char指针一样处理,并且仍然必须添加元素的sizeof大小。如果它仍然保持2D数组类型,那么通过添加1来跨越整个行。 - Johannes Schaub - litb
对于 int table[rows][cols];(table + integer_expression) 不正确。在 (table + integer_expression) 中,table 被转换为 int (*)[cols] - Daniel Fischer

20

一个二维数组被视为由一维数组组成的数组。也就是说,二维数组中的每一行都是一个一维数组。因此,对于给定的二维数组A

int A[m][n].

总的来说,

A[i][j] = *(A[i]+j) 
A[i] = *(A+i)
A[i][j] = *(A[i]+j) = * ( *(A+i)+j).

2
非常接近点赞了,但最后一句话太不正确了,不能直接点赞。二维数组(int arr[rows][cols];)不应与双指针(int **pp;)混淆。如果您删除它,我将非常高兴地点赞。 - Daniel Fischer

6
之前的答案已经解释得非常清楚了,我只是根据自己的理解列出指针表达式,并将它们与arr[i][j]格式进行比较。 2-D数组的指针表达式: 数组名称本身是第一个子数组的指针, arr: 将成为第一个子数组的指针,而不是第一个子数组的第一个元素,根据数组和指针之间的关系,它也代表着数组本身,
arr+1: 将成为第二个子数组的指针,而不是第一个子数组的第二个元素,
*(arr+1): 将成为第二个子数组的第一个元素的指针, 根据数组和指针之间的关系,它也表示第二个子数组,与arr[1]相同,
*(arr+1)+2: 将成为第二个子数组的第三个元素的指针,
*(*(arr+1)+2): 将获得第二个子数组的第三个元素的值, 与arr[1][2]相同。
与2-D数组类似,多维数组也有类似的表达式。

2
一种使用指针访问的实用方法。
typedef struct
{
    int  Array[13][2];
} t2DArray;

t2DArray TwoDArray =
{
   { {12,5},{4,8},{3,6},{7,9},{3,2},{3,3},{3,4},{3,5},{3,6},{3,7},{4,0},{5,0},{5,1} }
};

t2DArray *GetArray;

int main()
{
    GetArray = &TwoDArray;
    printf("\n %d\n %d\n %d\n %d\n %d\n %d\n",
    GetArray->Array[0][0], 
    GetArray->Array[0][1], 
    GetArray->Array[1][0], 
    GetArray->Array[1][1], 
    GetArray->Array[2][0], 
    GetArray->Array[2][1]);

    getchar();
    return 0;
}

输出

12 5 4 8 3 6


0
#include <iostream>
using namespace std;

int main()
{
   //FOR 1-D ARRAY THROUGH ARRAY
   int brr[5]= {1,2,3,4,5};

   for(int i=0; i<5; i++)
   {
      cout<<"address ["<<i<<"] = "  <<&brr[i]<<" and value = "<<brr[i]<<endl;        
   }

   //FOR 1-D ARRAY THROUGH POINTER
   cout<<endl;  //  endl TO MAKE OUT PUT LOOK CLEAR AND COOL :)
   int (*q)=brr;

   for(int i=0; i<5; i++)
   {
      cout<<"address ["<<i<<"] = "  <<&brr[i]<<" and value = "<<*(q+i)<<endl; //(p[i][j])
   }

   cout<<endl;

   //FOR 2-D ARRAY THROUGH ARRAY        
   int arr[2][3] = {1,2,3,4,5,6};

   for(int i=0; i<2; i++)
   {
      for(int j=0; j<3; j++)
      {
         cout<<"address ["<<i<<"]["<<j<<"] = "  <<&arr[i][j]<<" and value = "<<arr[i][j]<<endl;
      }
   }

   //FOR 2-D ARRAY THROUGH POINTER        
   int (*p)[3]=arr; //  j value we give
   cout<<endl;

   for(int i=0; i<2; i++)
   {
      for(int j=0; j<3; j++)
      {
         cout<<"address ["<<i<<"]["<<j<<"] = "  <<(*(p+i)+j)<<" and value = "<<(*(*(p+i)+j))<<endl; //(p[i][j])
      }
   }
   return 0;
}

==============OUT PUT======================

//FOR 1-D ARRAY THROUGH ARRAY

address [0] = 0x28fed4 and value = 1
address [1] = 0x28fed8 and value = 2
address [2] = 0x28fedc and value = 3
address [3] = 0x28fee0 and value = 4
address [4] = 0x28fee4 and value = 5

//FOR 1-D ARRAY THROUGH POINTER

address [0] = 0x28fed4 and value = 1
address [1] = 0x28fed8 and value = 2
address [2] = 0x28fedc and value = 3
address [3] = 0x28fee0 and value = 4
address [4] = 0x28fee4 and value = 5

//FOR 2-D ARRAY THROUGH ARRAY

address [0][0] = 0x28fee8 and value = 1
address [0][1] = 0x28feec and value = 2
address [0][2] = 0x28fef0 and value = 3
address [1][0] = 0x28fef4 and value = 4
address [1][1] = 0x28fef8 and value = 5
address [1][2] = 0x28fefc and value = 6

//FOR 2-D ARRAY THROUGH POINTER

address [0][0] = 0x28fee8 and value = 1
address [0][1] = 0x28feec and value = 2
address [0][2] = 0x28fef0 and value = 3
address [1][0] = 0x28fef4 and value = 4
address [1][1] = 0x28fef8 and value = 5
address [1][2] = 0x28fefc and value = 6

请编辑您的答案并添加一些解释来说明您的解决方案。 - juzraai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接