Подтвердить что ты не робот

В чем разница между типами 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*?

4b9b3361

Ответ 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, в частности:

и, возможно, остальные разделы Массив и указатели.