Чтобы полностью понять это, вы должны усвоить следующие концепции:
Массивы - это не указатели!
Во-первых (и об этом достаточно сказано), массивы - это не указатели . Вместо этого в большинстве случаев они «распадаются» на адрес своего первого элемента, который может быть назначен указателю:
int a[] = {1, 2, 3};
int *p = a; // p now points to a[0]
Я предполагаю, что это работает таким образом, чтобы можно было получить доступ к содержимому массива, не копируя его все. Это всего лишь поведение типов массивов и не означает, что это одно и то же.
Многомерные массивы
Многомерные массивы - это всего лишь способ «разбить» память таким образом, чтобы компилятор / компьютер мог понять и оперировать.
Например, int a[4][3][5]
= массив, содержащий 4 * 3 * 5 (60) «блоков» целочисленной памяти.
Преимущество перед использованием int a[4][3][5]
vs plainint b[60]
заключается в том, что они теперь «разделены» (при необходимости легче работать с их «кусками»), и теперь программа может выполнять проверку привязки.
Фактически, они int a[4][3][5]
хранятся точно так же, как int b[60]
в памяти - с той лишь разницей, что программа теперь управляет им, как если бы они были отдельными объектами определенных размеров (в частности, четыре группы по три группы по пять).
Имейте в виду: оба int a[4][3][5]
и int b[60]
одинаковы в памяти, и единственная разница в том, как они обрабатываются приложением / компилятором.
{
{1, 2, 3, 4, 5}
{6, 7, 8, 9, 10}
{11, 12, 13, 14, 15}
}
{
{16, 17, 18, 19, 20}
{21, 22, 23, 24, 25}
{26, 27, 28, 29, 30}
}
{
{31, 32, 33, 34, 35}
{36, 37, 38, 39, 40}
{41, 42, 43, 44, 45}
}
{
{46, 47, 48, 49, 50}
{51, 52, 53, 54, 55}
{56, 57, 58, 59, 60}
}
Из этого вы можете ясно видеть, что каждый «раздел» - это просто массив, который программа отслеживает.
Синтаксис
Теперь массивы синтаксически отличаются от указателей . В частности, это означает, что компилятор / машина будет относиться к ним по-разному. Это может показаться очевидным, но взгляните на это:
int a[3][3];
printf("%p %p", a, a[0]);
В приведенном выше примере один и тот же адрес памяти печатается дважды, например:
0x7eb5a3b4 0x7eb5a3b4
Однако только один может быть назначен указателю напрямую :
int *p1 = a[0]; // RIGHT !
int *p2 = a; // WRONG !
Почему нельзя a
назначить указателю, а a[0]
можно?
Это просто следствие многомерных массивов, и я объясню почему:
На уровне « a
» мы все еще видим, что у нас есть еще одно «измерение», которого стоит ожидать. На уровне 'a[0]
' мы уже находимся в верхнем измерении, поэтому, что касается программы, мы просто смотрим на обычный массив.
Вы можете спросить:
Почему имеет значение, если массив является многомерным, с точки зрения создания на него указателя?
Лучше всего думать так:
«Распад» из многомерного массива - это не просто адрес, а адрес с данными раздела (он также понимает, что его базовые данные состоят из других массивов), который состоит из границ, установленных массивом за пределами первого измерения.
Эта логика «разделения» не может существовать внутри указателя, если мы ее не укажем:
int a[4][5][95][8];
int (*p)[5][95][8];
p = a; // p = *a[0] // p = a+0
В противном случае значение свойств сортировки массива теряется.
Также обратите внимание на использование круглых скобок вокруг *p
: int (*p)[5][95][8]
- Это означает, что мы создаем указатель с этими границами, а не массив указателей с этими границами:int *p[5][95][8]
Вывод
Давайте рассмотрим:
- Массивы распадаются на адреса, если они не имеют другого назначения в используемом контексте
- Многомерные массивы - это просто массивы массивов. Следовательно, «испорченный» адрес будет нести бремя «У меня есть дополнительные измерения».
- Данные измерения не могут существовать в указателе, если вы не передадите его ему .
Вкратце: многомерные массивы распадаются до адресов, которые несут в себе способность понимать их содержимое.