Я знаю, что для одномерных массивов x=a[i]
эквивалентно x=*(a+i)
, но как я могу обращаться к элементам двумерных массивов с помощью указателей?
Как использовать выражения указателя для доступа к элементам двумерного массива в C?
Ответ 1
Резюме: Если у вас многомерный массив, определенный как int [][]
, тогда x = y[a][b]
эквивалентен x = *((int *)y + a * NUMBER_OF_COLUMNS + b);
Детали расточки:
Приведение (int *)
y
выше заслуживает некоторого объяснения, так как его необходимость не может быть вначале интуитивной. Чтобы понять, почему это должно быть, рассмотрим следующее:
-
Арифметика типизированных указателей в C/С++ всегда корректирует введенное значение указателя (которое является адресом) размером типа в байтах при добавлении/вычитании/приращении/уменьшении по скалярному.
-
Фундаментальный тип объявления многомерного массива (а не тип элемента; тип переменной) - это тип массива безразмерного измерения, чем конечный размер.
Последний (№ 2) из них действительно нуждается в примере, чтобы затвердеть. В дальнейшем переменные ar1
и ar2
являются эквивалентными объявлениями.
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!
Ответ 2
Таблица
В C 2D-массивах есть непрерывные ряды строк (не как в Паскале).
Когда мы кладем таблицу целых чисел с 4 строками и 5 столбцами:
Достижение элементов
Мы можем достичь элементов с помощью:
int element = table[row-1][column-1];
Но мы также можем сделать это со следующим кодом:
int element = *(*(table+row-1)+column-1);
В этих примерах row
и column
подсчитывается от 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
вернет значение первого элемента. В следующем примере вы можете видеть, что *table
и table
указывают на тот же адрес памяти.
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);
В памяти
Ответ 3
2D-массив рассматривается как массив из 1D массивов. То есть каждая строка в двумерном массиве представляет собой 1D-массив. Поэтому, учитывая 2D-массив A
,
int A[m][n].
В общем случае
A[i][j] = *(A[i]+j)
также
A[i] = *(A+i)
поэтому
A[i][j] = *(A[i]+j) = * ( *(A+i)+j).
Ответ 4
Предыдущие ответы уже объяснялись очень хорошо, Я бы просто перечислил выражения указателей в соответствии с моим пониманием и сравнил их с форматом arr [i] [j].
Pointer expression of 2-D array: the array name itself is a pointer to first sub array, arr: will be pointer to first sub array, not the first element of first sub array, according to relationship of array & pointer, it also represent the array itself, arr+1: will be pointer to second sub array, not the second element of first sub array, *(arr+1): will be pointer to first element of second sub array, according to relationship of array & pointer, it also represent second sub array, same as arr[1], *(arr+1)+2: will be pointer to third element of second sub array, *(*(arr+1)+2): will get value of third element of second sub array, same as arr[1][2],
Подобно 2-D массиву, массив multiple-D имеет аналогичное выражение.
Ответ 5
Практический способ доступа с помощью указателя.
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
Ответ 6
#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