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

Почему нам нужно указывать размер столбца при передаче 2D-массива в качестве параметра?

Почему мой параметр не может быть

void example(int Array[][]){ /*statements*/}

Почему мне нужно указать размер столбца массива? Скажем, например, 3

void example(int Array[][3]){/*statements*/}

Мой профессор сказал, что он является обязательным, но я был кодированием до начала школы, и я вспомнил, что не было синтаксической или семантической ошибки, когда я сделал этот параметр? Или я что-то пропустил?

4b9b3361

Ответ 1

Когда дело доходит до описания параметров, массивы всегда распадаются на указатели на их первый элемент.

Когда вы передаете массив, объявленный как int Array[3], функции void foo(int array[]), он распадается на указатель на начало массива i.e. int *Array;. Btw, вы можете описать параметр как int Array[3] или int array[6] или даже int *array - все это будет эквивалентно, и вы можете без проблем передать любой целочисленный массив.

В случае массивов массивов (2D-массивов) он также распадается на указатель на его первый элемент, который оказывается одномерным массивом, то есть мы получаем int (*Array)[3].

Указание размера здесь важно. Если это не является обязательным, компилятор не сможет каким-либо образом понять, как обращаться с выражением Array[2][1], например.

Чтобы разыменовать, что компилятору необходимо вычислить смещение элемента, который нам нужен в непрерывном блоке памяти (int Array[2][3] - непрерывный блок целых чисел), который должен быть простым для указателей. Если a является указателем, то a[N] расширяется как start_address_in_a + N * size_of_item_being_pointed_by_a. В случае выражения Array[2][1] внутри функции (мы хотим получить доступ к этому элементу) Array является указателем на одномерный массив и применяется та же формула. Количество байтов в последней квадратной скобке требуется для поиска size_of_item_being_pointed_by_a. Если бы у нас было просто Array[][], было бы невозможно найти его и, следовательно, невозможно разыменовать элемент массива, который нам нужен.

Без размера, арифметика указателей не будет работать для массивов массивов. Каким будет адрес Array + 2: продвиньте адрес в Array 2 байта вперед (неправильно) или переместите указатель 3* sizeof(int) * 2 байты вперед?

Ответ 2

В C/С++ даже 2-D массивы сохраняются последовательно, одна строка за другой в памяти. Итак, когда у вас (в одной функции):

int a[5][3];
int *head;

head = &a[0][0];
a[2][1] = 2; // <--

Элемент, к которому вы действительно обращаетесь с a[2][1], *(head + 2*3 + 1), вызывает последовательно, этот элемент после трех элементов строки 0 и 3 элемента строки 1, а затем еще один индекс далее.

Если вы объявите функцию вроде:

void some_function(int array[][]) {...}

синтаксически, это не должно быть ошибкой. Но когда вы пытаетесь получить доступ к array[2][3] сейчас, вы не можете определить, к какому элементу должен быть обращен доступ. С другой стороны, когда у вас есть:

void some_function(int array[][5]) {...}

вы знаете, что с помощью array[2][3] можно определить, что вы действительно обращаетесь к элементу по адресу памяти *(&array[0][0] + 2*5 + 3) , потому что функция знает размер второго измерения.

Существует еще один вариант, как было предложено ранее, вы можете объявить такую ​​функцию, как:

void some_function(int *array, int cols) { ... }

потому что таким образом вы вызываете функцию с той же "информацией", что и раньше, - количеством столбцов. Вы обращаетесь к элементам массива немного иначе: вам нужно написать *(array + i*cols + j), где вы обычно пишете array[i][j], потому что array теперь является указателем на целое число (не на указатель).

Когда вы объявляете такую ​​функцию, вы должны быть осторожны, чтобы вызвать ее с количеством столбцов, которые фактически объявлены для массива, а не только. Итак, например:

int main(){
   int a[5][5];
   int i, j;

   for (i = 0; i < 3; ++i){
       for (int j=0; j < 3; ++j){
           scanf("%d", &a[i][j]);
       }
   }

   some_function(&a[i][j], 5); // <- correct
   some_function(&a[i][j], 3); // <- wrong

   return 0;
}

Ответ 3

В этом отношении есть аналогичная статья. Вы можете ссылаться на ссылку ниже. Создание массива в C и передача указателя на указанный массив Надеюсь, что это поможет.

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

Ответ 4

Когда вы создаете 2D-массив, anytype a[3][4], в памяти то, что вы на самом деле создаете, является 3 смежными блоками объектов 4 anytype.

a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]

Теперь следующий вопрос: почему это так? Поскольку, соблюдая спецификацию и структуру языка, anytype a[3][4] фактически расширяется в anytype (*a)[4], потому что массивы распадаются на указатели. И на самом деле это также распространяется на anytype (*(*a)), однако теперь вы полностью потеряли размер 2D-массива. Итак, вы должны немного помочь компилятору.

Если вы запросите программу для a[2], программа может выполнить те же действия, что и для 1D-массивов. Он просто может вернуть the 3rd element of sizeof(object pointed to), объект, на который указывает здесь, имеет объекты anytype размером 4.

Ответ 5

Все это связано с представлением внутреннего массива. Таким образом, массив, подобный int arr_2 [2][2] = {{1,2},{3,4}};, выглядит в памяти как 1,2,3,4, каждый из которых является целым типом. arr_2 примерно эквивалентен &arr[0][0] (указатель на первый элемент) при передаче функции.

Обычные массивы хранятся и получаются аналогично int arr[2] = {2,5}; в памяти 2,5. Компилятор добирается до них через свою базу плюс время смещения типа base_ptr + index * type. Чтобы получить значение, хранящееся в индексе 1 типа int, пишем &arr[0] + 1 * sizeof(int) = 5

Итак, двумерные массивы доступны так base_ptr + type * 2nd_dem_len + index. Чтобы получить значение, сохраненное в [1] [1], пишем &arr_2[0][0] + sizeof(int) * 1 + 1 = 4

Поэтому, если компилятор не знает 2nd_dem_len или длину второго измерения, он не может получить доступ к этому массиву. Он не знает этого, потому что массив попадает в это представление при передаче функции, потому что компилятор преобразует его в указатель, который не имеет информации о размере.

Ответ 6

Собственно, будь то массив 2d или 1d, он сохраняется в памяти в одной строке. Поэтому, чтобы сказать компилятор, где он должен разорвать строку, указывающую, что следующие числа будут в следующих строках, мы предполагаем для обеспечения размера столбца. И разбиение строк соответствующим образом даст размер строк.

Посмотрим на пример:

int a[][3]={ 1,2,3,4,5,6,7,8,9,0 };

Этот массив a сохраняется в памяти как:

  1  2  3  4  5  6  7  8  9  0

Но поскольку мы указали размер столбца как 3, память разбивается после каждых трех чисел.

#include<stdio.h>

int main() {
   int a[][3]={1,2,3,4,5,6},i,j;
   for(i=0;i<2;i++)
   {
       for(j=0;j<3;j++)
       {
           printf("%d  ",a[i][j]);
       }
       printf("\n");
   }

}

ВЫВОД:

 1  2  3  
 4  5  6  

В другом случае

int a[3][]={1,2,3,4,5,6,7,8,9,0};

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

#include<stdio.h>

int main() {
   int a[3][]={1,2,3,4,5,6},i,j;
   for(i=0;i<3;i++)
   {
       for(j=0;j<2;j++)
       {
           printf("%d  ",a[i][j]);
       }
       printf("\n");
   }

}

ВЫВОД:

 c: In function 'main':
    c:4:8: error: array type has incomplete element type 'int[]'
    int a[3][]={1,2,3,4,5,6},i,j;
        ^