Почему я могу привести из int ** (как двухмерный массив) к int *? - PullRequest
0 голосов
/ 02 мая 2020

Я написал код -

int arr2d[2][2] = {{1, 2}, {3, 4}};
int * arr = (int*)arr2d;
int i = 0;
for(i = 0; i < 4; i++)
{
    printf("%d  ", arr[i]);
}

Вывод был таким, как будто я печатал каждый элемент в массивах в arr2d, ничего необычного. Почему это так?

Ответы [ 3 ]

1 голос
/ 02 мая 2020

Почему я могу привести от int ** (в виде двухмерного массива) к int *?

У вас неправильное понимание. Массивы не являются указателями . В большинстве случаев они распадаются на указатели, но это вопрос оценки , а не природы. Соответственно, двумерные массивы являются массивами массивов, а не массивами указателей. Таким образом, они не распадаются на указатели на указатели, а скорее на указатели на массивы. В вашем коде нигде не задействовано int**.

С учетом этого объявления:

int arr2d[2][2] = {{1, 2}, {3, 4}};

Соответствующее присвоение указателя, которое вы можете выполнить без преобразования,

int (*arr2d_ptr)[2];

arr2d_ptr = arr2d;  // arr2d is not a pointer, but it does decay to one in this expression

arr2d_ptr - указатель на массив из двух элементов int. Назначение указывает на первый элемент arr2d. Если вы преобразуете это в тип int *, то результат указывает на первый int в массиве, на который указывает arr2d_ptr. Например,

int *ip = (int *) arr2d_ptr;

Это естественно, потому что int - это точно первая часть массива. Вы можете получить к нему доступ по индексу как ip[0] или *ip. И вы можете получить доступ ко второму int в этом массиве как ip[1].

Я предполагаю, что другой аспект вопроса касается выражений ip[2] и ip[3]. Массивы являются смежными последовательностями элементов. Массивы массивов не являются особенными в этом отношении: они представляют собой непрерывные последовательности (меньших) массивов. Таким образом, макет вашего arr2d выглядит следующим образом:

array..|array..|

. Если вы наложите макет каждого из массивов-членов, то получите следующее:

int|int|int|int|

, что в точности совпадает с макетом одномерного массива из четырех int. Вот почему вы можете получить доступ ко всем четырем int с помощью индексации ip (или arr в вашем примере кода).

Интересный факт : потому что выражения типа массива затухают в указатели, вам не нужен актерский состав здесь. Вместо этого вы можете разыменовать arr2d_ptr, чтобы сформировать выражение, обозначающее первый элемент двумерного массива, который является одномерным массивом, и позволить этому затуханию указателя:

int *ip2 = *arr2d_ptr;

или эквивалентно

int *ip3 = *arr2d;
0 голосов
/ 03 мая 2020

В памяти массив char a[4] = {1, 2, 3, 4} выглядит следующим образом:

[(1)(2)(3)(4)]

с (), представляющим байт памяти, и [], указывающим, где начинается массив / заканчивается.

Массив char a[2][2] = {{1, 2}, {3, 4}} выглядит в памяти следующим образом:

[[(1)(2)][(3)(4)]]

Видите, разницы нет, поскольку [] на самом деле не существует в памяти они просто подсказки, которые я использовал в своем ответе. Фактически, если вы ничего не знаете о массивах и просто просматриваете содержимое необработанных байтов в памяти, вы увидите:

(1)(2)(3)(4)

в в обоих case.

Таким образом, вместо создания массива a[Xmax][Ymax] и доступа к элементам с помощью a[x][y], вы также можете создать массив как b[Xmax * Ymax] и обращаться к элементам с помощью b[x * Xmax + y], так как это на самом деле то, что происходит за кулисами в любом случае.

А в C вы всегда можете превратить ссылку на массив в указатель, так как ссылка на массив является ссылкой на область памяти, где расположен массив, и указатель является ссылкой на область памяти (независимо от того, находится ли там массив или нет). Так что

int a[5] = { ... };
int * b = a;

работает как a - это ссылка на массив int, который представляет собой всего несколько значений int, хранящихся в памяти, а b - указатель на область памяти, где хранится значение int. , Ну, по адресу, с которого начинается массив a, сохраняется значение int , так что это присваивание совершенно правильно.

И m[3] просто означает " увеличить адрес памяти m ссылается три раза по размеру типа m ссылки и извлекает значение из этого адреса". И это работает и возвращает ожидаемое значение, независимо от того, является ли m указателем или ссылкой на массив. Таким образом, хотя этот синтаксис на самом деле предназначен для доступа к массиву, он также работает с указателями.

Синтаксис для указателя будет на самом деле *(m + 3), что означает, что " увеличит адрес указателя в три раза на размер типа значения, на которое он указывает, а затем извлекает значение из этого адреса". Тем не менее, только в первом случае этот синтаксис будет работать и со ссылкой на массив, так как ссылка на массив всегда может стать указателем при необходимости.

0 голосов
/ 02 мая 2020

Значения хранятся в памяти, как если бы это был линейный массив. Вот почему вы можете получить к ним доступ таким образом.

Это не работает, когда двумерный массив состоит из указателей, и я должен понять, почему.

Если вы работаете с массивом указателей, вам придется использовать указатель на указатель, или двойной указатель.

Кажется логичным c, что если вы хотите, чтобы указатель обращался к элементам в массиве указателей, вам понадобится указатель на указатель.

//...
int *arr2d[2][2];
int **arr = (int**)arr2d;
//...

Если вы хотите его использовать в качестве двумерного массива, который вам необходим в большинстве случаев, вы можете использовать:

Живой образец

#include <stdio.h>
#include <string.h>

int main()
{
    int arr2d[2][2] = {{1, 2}, {3, 4}};

    int(*arr)[2] = arr2d; //pointer to 2d array with 2 columns

    for (int i = 0; i < 2; i++){
        for(int j = 0; j < 2; j++){
            printf("%d  ", arr[i][j]); //use it as if it were a 2d array
        }
        putchar('\n');
    }
}

Двойной указатель не следует путать с двумерным массивом это разные вещи, этот поток имеет хорошее объяснение того, что означает каждый из них.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...