В чем разница между типами int ** и int [] []?
Если выполняется следующее присваивание:
int a[2] = {1,2};
int* b = a;
то что с этим не так:
int a[2][2]={1,2,3,4};
int** b = a;
С++ дает ошибку, что он не может преобразовать int[][]
в int**
. В чем разница между двумя типами, если int[]
совпадает с int*
?
Ответы
Ответ 1
Успокойся. Это только ошибка компилятора. Массивы довольно сложны. Вот правило:
Значение переменной массива распадается на адрес элемента этого массива
Ваш первый фрагмент выглядит следующим образом:
int a[2] = {1,2};
Итак, согласно правилу, если a
находится в правой части присваивания, оно распадается на адрес элемента нуль и поэтому имеет тип int *
. Это приведет вас к
int *b = a;
Во втором фрагменте у вас действительно есть массив массивов. (Кстати, чтобы сделать это явным, я немного изменил ваш код.)
int a[2][2]={{1,2},{3,4}};
На этот раз a
будет распадаться на указатель на массив из двух целых чисел! Поэтому, если вы хотите присвоить a
что-то, вам понадобится это, чтобы иметь тот же тип.
int (*b)[2] = a; //Huh!
(Этот синтаксис может быть немного ошеломляющим для вас, но просто подумайте на мгновение, что мы написали int *b[2];
Got the point? b
будет массивом указателей на целые числа! Не совсем то, что мы хотели... )
Вы можете перестать читать здесь, но вы тоже можете двигаться дальше, потому что я не сказал вам всю правду. Правило, о котором я упоминал, имеет три исключения...
Значение массива будет не затухать до адреса элемента 0 , если
- массив - это операнд
sizeof
- массив - это операнд
&
- array - это литеральный инициализатор строки для массива символов
Объясните эти исключения более подробно и с примерами:
int a[2];
int *pi = a ; /* the same as pi = &a[0]; */
printf("%d\n", sizeof(a)); /* size of the array, not of a pointer is printed! */
int (*pi2)[2] = &a; /* address of the array itself is taken (not the address of a pointer) */
И наконец
char a[] = "Hello world ";
Здесь не указана указатель на "Hello world", но вся строка копируется и указывает на эту копию.
На самом деле очень много информации, и на самом деле трудно понять все сразу, поэтому не спешите. Я рекомендую вам прочитать K & R по этой теме, а затем эту отличную книгу.
Ответ 2
Это очень много, поэтому я попытаюсь объяснить это так ясно, как только смогу.
Когда вы создаете массив, он сохраняет элементы смежно в памяти, поэтому:
int arr[2] = { 1, 2 };
Переведено на:
arr:
+---+---+
| 1 | 2 |
+---+---+
Указатель указывает на объект в памяти, а при разыменовании через унарный *
или через []
он обращается к этой непрерывной памяти. Итак, после
int *ptr = arr;
ptr
(или &ptr[0]
, если хотите) указывает на поле 1
, и ptr + 1
(или &ptr[1]
) указывает на поле 2
. Это имеет смысл.
Но если массивы смежны в памяти, массивы массивов смежны в памяти. Итак:
int arr[2][2] = {{ 1, 2 }, { 3, 4 }};
Выглядит в памяти следующим образом:
arr:
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
Который очень похож на наш плоский массив.
Теперь рассмотрим, как будет указываться указатель на указатель на int
в памяти:
ptr:
+-------+-------+
| &sub1 | &sub2 |
+-------+-------+
sub1:
+---+---+
| 1 | 2 |
+---+---+
sub2:
+---+---+
| 3 | 4 |
+---+---+
ptr
(или &ptr[0]
) указывает на sub1
, а ptr + 1
(или &ptr[1]
) указывает на sub2
. sub1
и sub2
не имеют фактического отношения друг к другу и могут быть в любом месте в памяти, но поскольку он является указателем на указатель, двоякая разметка двумерного массива сохраняется, хотя структура памяти несовместима.
Массивы типа T
распадаются на указатели на тип T
, но массивы массивов типа T
не распадаются на указатели на указатели на тип T
, они распадаются на указатели на массивы типа T
. Поэтому, когда наш 2D arr
распадается на указатель, он не является указателем на указатель на int
, а на указатель на int [2]
. Полное имя этого типа - int (*)[2]
, и чтобы сделать вашу работу с кодом, вы хотели бы использовать
int (*ptr)[2] = arr;
Какой правильный тип. ptr
ожидает указать на непрерывный массив памяти, например arr
делает - ptr
(или &ptr[0]
) указывает на arr
и ptr + 1
(или &ptr[1]
) указывает на &arr[1]
. ptr[0]
указывает на поле, которое содержит 1
, а ptr[1]
указывает на поле, которое содержит 3
, поэтому ptr[0][0]
дает 1, ptr[0][1]
дает 2 и т.д.
Зачем вам это знать? 2D-указатели кажутся более сложными, чем они того стоят - если бы вы использовали malloc
, вам пришлось бы вызывать malloc
несколько раз в цикле и делать то же самое для free
. ИЛИ, вы могли бы использовать какой-то хитрый трюк, чтобы сделать плоское одномерное распределение памяти как 2D-массив:
// x and y are the first and second dimensions of your array
// so it would be declared T arr[x][y] if x and y were static
int (*arr)[y] = malloc(x * y * sizeof(arr[0][0]));
if(!arr) /* error */;
Теперь arr
указывает на непрерывный блок массивов размером y
объектов int
. Поскольку объект, на который он указывает, является массивом, нам не нужна двойная указатель-косвенность объектов int **
, и когда вы закончите, вы можете освободить его одним вызовом:
free(arr);
Сравните это с версией с помощью int **
:
int **arr = malloc(x * sizeof(*arr));
if(!arr) /* error */;
for(size_t ii = 0; ii < x; ii++)
{
arr[ii] = malloc(y * sizeof(**arr));
if(!arr[ii])
{
free(arr[ii]);
free(arr);
}
}
// do work
for(size_t ii = 0; ii < x; ii++)
free(arr[ii]);
free(arr);
Приведенный выше код имеет утечку памяти. Посмотрите, можете ли вы его найти. (Или просто используйте версию с такими, казалось бы, сложными указателями на массивы.)
Ответ 3
Знаменитое соглашение распада: массив рассматривается как указатель, указывающий на первый элемент массива.
int a[2] = {1,2};
int* b = a; //decay
Но соглашение о распаде не должно применяться более одного раза к одному и тому же объекту.
int a[2][2]={1,2,3,4};
int** b = a; //decay more than once
Ответ 4
int[]
не совпадает с int*
. int[]
будет распадаться на int*
в определенных контекстах.
Вам, вероятно, следует прочитать comp.lang.c FAQ, в частности:
и, возможно, остальные разделы Массив и указатели.