Поведение 2D-массивов
Я создал 2D-массив и попытался напечатать определенные значения, как показано ниже:
int a[2][2] = { {1, 2},
{3, 4}};
printf("%d %d\n", *(a+1)[0], ((int *)a+1)[0]);
Вывод:
3 2
Я понимаю, почему 3
- это первый вывод (a+1
указывает на вторую строку, и мы печатаем его элемент 0th
.
Мой вопрос касается второго выхода, т.е. 2
. Я предполагаю, что из-за typecasting a
как int *
, 2D-массив обрабатывается как 1D-массив, и поэтому a+1
действует как указатель на элемент 2nd
, и поэтому мы получаем вывод как 2
.
Правильны ли мои предположения или есть ли какая-то другая логика?
Кроме того, изначально какой тип a
обрабатывается как указатель int (*)[2]
или int **
?
Ответы
Ответ 1
Правильны ли мои предположения или есть ли другая логика?
Да.
*(a+1)[0]
эквивалентен a[1][0]
.
((int *)a+1)[0]
эквивалентно a[0][1]
.
Объяснение:
a
распадается на указатель на первый элемент 2D-массива, то есть на первую строку. *a
разделяет эту строку, которая представляет собой массив из 2 int
. Поэтому *a
можно рассматривать как имя массива первой строки, которое далее распадается на указатель на его первый элемент, т.е. 1
. *a + 1
даст указатель на второй элемент. Выделение *a + 1
даст 1
. Итак:
((int *)a+1)[0] == *( ((int *)a+1 )+ 0)
== *( ((int *)a + 0) + 1)
== a[0][1]
Обратите внимание, что a
, *a
, &a
, &a[0]
и &a[0][0]
все имеют одинаковое значение адреса, хотя они имеют разные типы. После распада a
имеет тип int (*)[2]
. Отбрасывая его на int *
, просто вводится значение адреса int *
, а арифметика (int *)a+1
указывает адрес второго элемента.
Также, изначально какой тип a обрабатывается как указатель (int (*)[2]
или int **
?
Он становится указателем типа на массив из 2 int
, i.e int (*)[2]
Ответ 2
Когда вы написали выражение
(int *)a
то логически исходный массив можно рассматривать как одномерный массив следующим образом
int a[4] = { 1, 2, 3, 4 };
Таким образом, выражение a
указывает на первый элемент, равный 1 этого мнимого массива. Выражение ( a + 1 )
указывает на второй элемент мнимого массива, равный 2, а выражение ( a + 1 )[0]
возвращает ссылку на этот элемент, который вы получаете 2.
Ответ 3
2D-массив - это, по сути, одномерный массив с некоторыми дополнительными знаниями компилятора.
Когда вы отбрасываете a
в int*
, вы удаляете это знание, и оно обрабатывается как обычный одномерный массив (который в вашем случае выглядит в памяти, например 1 2 3 4
).
Ответ 4
Ключевое значение для распознавания здесь - это то, что a
содержит значение адреса, в котором находится первая строка. Поскольку весь массив начинается с того же места, что и весь массив, он имеет одинаковое значение адреса; то же самое для самого первого элемента.
В C
термины:
&a == &a[0];
&a == &a[0][0];
&a[0] == &a[0][0];
// all of these hold true, evaluate into 1
// cast them if you want, looks ugly, but whatever...
&a == (int (*)[2][2]) &a[0];
&a == (int (*)[2][2]) &a[0][0];
&a[0] == (int (*)[2]) &a[0][0];
По этой причине, когда вы отбрасываете a
в int *
, он просто становится равным 1 к 1 эквиваленту &a[0][0]
как с помощью типа, так и значения. Если вы должны применить эти операции к &a[0][0]
:
(&a[0][0] + 1)[0];
(a[0] + 1)[0];
*(a[0] + 1);
a[0][1];
Что касается типа a
, если он рассматривается как указатель, хотя я не уверен, должен быть int (*)[2]
.