Передача массивов в функции без использования указателей - PullRequest
0 голосов
/ 15 сентября 2018

C позволяет передавать массивы в функции, используя имя массива напрямую. Поскольку имя массива является начальным адресом массива (адрес элемента [0]) - он передается по ссылке - следовательно, даже локальный массив может быть передан таким образом - и изменения любой функции будут видны другой без какой-либо необходимость явного возврата. Что я понимаю до сих пор -

  1. В вызове функции просто необходимо указать имя массива, предоставляющего начальный адрес - и целочисленный размер каждого измерения в качестве отдельных аргументов int - таким образом, любая функция, кроме функции, содержащей определение массива, имеет вся информация о расположении и размере массивов (размер массива, а также размер каждого измерения) и может работать с любым элементом. Еще раз, поскольку это передача по ссылке, любое такое изменение будет видимым для всех таких функций, включая и из той, которая содержит определение массива (для каждой функции потребуется передать ту же информацию, что и аргументы - имя массива для начального адреса и каждый размер измерения), без какого-либо возврата, необходимого специально для того, чтобы эти изменения были видны всем функциям, включая вызывающую функцию.

  2. Аргумент массива в прототипе функции и определении функции указывает массив с парой квадратных скобок для каждого измерения + в том же аргументе - самый большой? также указывается значение для каждого измерения, кроме самого значимого (строки в двумерном массиве), например -

    //Prototype  
    return_type function_name ( array_name[][MAX_COLS], int rows, int cols);
    
    // Function call somewhere  
    function_name (array_name, num_rows, num_columns); /* array name refers 
    to starting address */
    

Также для размеров, для которых самый большой? Значение указывается в прототипе и определении (MAX_COLS для нашего примера), фактическое значение во время вызова, указанное в качестве отдельного аргумента (столбцы), может и будет главным образом отличаться.

Я не знаю, почему размер требуется для других измерений (MAX_COLS), и это будет очень любезно, если кто-то объяснит. Также, пожалуйста, исправьте, подтвердите, исправьте все по мере необходимости.

Пример кода с одномерным массивом указателей для моделирования двумерного массива -

  #define MAX_ROWS 20

extern void modify( int rows, int cols, int *a[]);  
extern void input_2d_array(int rows, int cols, int *a[]); 
extern void output_2d_array(int rows, int cols, int *a[]);


int main()

{

int rows = 4, cols = 3;

int* a[MAX_ROWS], i;

for (i=0; i<rows; i++)
    a[i] = (int*) malloc ( cols*sizeof(int) );

input_2d_array(rows, cols, a);

printf("Initially in main array = \n");
output_2d_array(rows, cols, a);

modify(rows, cols, a);

printf("Finally in main array = \n");
output_2d_array(rows, cols, a);

_getch();
return 0;

}

void input_2d_array(int rows, int cols, int *a[])
{

int i, j;

for (i = 0; i < rows; i++)
    for (j = 0; j < cols; j++)
    {
        printf("Please enter the elements for row %d and column %d of a \n", i + 1, j + 1);
        scanf_s(" %d", (a+(i*cols)+j) );
    }

return;
}

void output_2d_array(int rows, int cols, int *a[])
{
int i, j;

for (i = 0; i < rows; i++)
{
    for (j = 0; j < cols; j++)
    {
        printf(" %d", *(a + (i*cols) + j) );
    }
    printf("\n");
}

return;
}

void modify(int rows, int cols, int *a[])
{
int i, j;

printf("Initally in modify array = \n");
output_2d_array(rows, cols, a);

for (i = 0; i < rows; i++)
{
    for (j = 0; j < cols; j++)
    {
        *(a + (i*cols) + j) = (*(a + (i*cols) + j)) + 2;
        // *(*(a + i) + j) += 2; // Gives exception
        // *(a[i]+j) += 10; // Gives exception

    }
}

printf("Finally in modify array = \n");
output_2d_array(rows, cols, a);

return;
}

Ответы [ 3 ]

0 голосов
/ 15 сентября 2018

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

Массивы фиксированной ширины

Ваш пример прототипа (более или менее):

//Prototype  
ReturnType function_name(ArrayType array_name[][MAX_COLS], int rows, int cols);

Где-то у вас есть постоянное определение для MAX_COLS;в качестве аргумента это может быть:

enum { MAX_COLS = 64 };

Эта функция принимает только массивы со столбцами MAX_COLS, хотя в массивах может быть любое количество строк.Аргумент int cols не имеет значения для определения этого массива.В вызывающем коде массив должен быть определен с MAX_COLS (или его эквивалентом) в качестве второго измерения.

Внутри функции компилятор будет интерпретировать ссылку array_name[i][j] (i и * 1021).* являющиеся целочисленными типами) как:

*(array_name + (i * MAX_COLS + j))

Он никогда не будет использовать cols в этом расчете.Попытка передать массив с другой шириной строки в функцию приводит к - я собираюсь назвать это «неопределенным поведением», хотя может быть строго другая интерпретация, для которой применимо нежелательное и не полностью определенное поведение.Результат не будет тем, что вы ожидаете.Если вы передадите более узкий массив, вы вполне можете индексировать его за пределы фактического массива;если вы передадите более широкий массив, вы, вероятно, не выйдете за пределы, но (как и при доступе за пределами) вы не будете получать доступ к элементам, к которым ожидаете получить доступ, потому что вычисления будут основаны на MAX_COLS,не аргумент функции cols.

Массивы переменной длины

Используя массив переменной длины (VLA), вы можете изменить свою функцию на:

ReturnType function_name(int rows, int cols, ArrayType array_name[rows][cols]);

Обратите внимание, чтопеременные размера должны быть определены до их использования;параметры размера должны предшествовать их использованию в определении массива.

Теперь внутри функции компилятор будет интерпретировать array_name[i][j] (как прежде) как:

*(array_name + (i * cols + j))

Вы можете передать любойформа массива, если вы получаете правильные параметры rows и cols.

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

ReturnType function_name(int rows, int cols, ArrayType array_name[*][*]);
ReturnType function_name(int rows, int cols, ArrayType array_name[][*]);

Тем не менее, определение функции должно иметь cols появиться перед определением массива:

ReturnType function_name(int rows, int cols, ArrayType array_name[rows][cols]) { … }
ReturnType function_name(int rows, int cols, ArrayType array_name[][cols]) { … }

И с «нестандартным» начальным измерением вы могли бы (если бы вы былидостаточно извращенно) write:

ReturnType function_name(int cols, ArrayType array_name[][*], int rows);
ReturnType function_name(int cols, ArrayType array_name[][cols], int rows) { … }

Мое собственное мнение состоит в том, что проще поддерживать код, если объявление прототипа функции точно соответствует строке определения функции, поэтому я не буду использовать нотации с *в прототипах.Я бы также использовал явное обозначение ArrayType array_name[rows][cols], указывающее, какой параметр определяет каждый размер.Это имеет значение, если вы выполняете функцию умножения матриц, например:

void MatrixMultiply(int r1, int c1, int c2,
                    Data m1[r1][c1], Data m2[c1][c2], Data result[r1][c2]);

Вопрос о том, будет ли компилятор сообщать о проблемах с несоответствиями размеров матриц, открыт для обсуждения, но такая возможность есть.Если вы предпочитаете, вы можете иметь избыточный параметр r2, чтобы указать размер строки матрицы m2, но тогда у вас возникнут проблемы, если r2 != c1, и компилятору будет труднее помочь - высводится к утверждениям или другим механизмам сообщения об ошибках, чтобы указать, что с результатом ничего не было сделано, потому что размеры матриц не были совместимы для умножения.

Я не уверен, что я разобрал все недоразумения в вашем вопросе;Я подозреваю, что нет.

Массивы указателей против 2D-массивов

Из комментария :

Если сигнатура моей функции void modify(int rows, int cols, int *a[]), токак мне получить доступ к a[i][j] и &a[i][j] в вызываемой функции?

Для этого требуется массив указателей на int, поэтому вы должны установить массив иначе, чем прежде (ввызывающий код).Однако внутри функции вы просто пишете:

int x = a[i][j];
int *y = &a[i][j];
int *z = a[i] + j;

Выражения для y и z эквивалентны;Вероятно, я бы использовал первое.

Обратите внимание, что вычисление «за кадром» для x здесь отличается от формулы, используемой для a[i][j] при передаче массива:

int x = *(*(a + i) + j);

Имеется две ссылки на память: первая для чтения указателя a[i], а вторая для чтения a[i][j] (с двумя явными добавлениями, но без явного умножения, хотя добавления нижнего индекса должны быть масштабированы до размера int * и int), тогда как в нотации массива была только одна ссылка на память (с двумя явными добавлениями и одним явным умножением - и только одна операция масштабирования размером int).

В том же комментарии также сказано:

Я попробовал комбинацию a[i]+j и (a[i]+j), а также *(a+(i*cols)+j) и (a+(i*cols)+j), но ни одна из них не работает.

Есть большая вероятность, что мне не удалось добавить символы * в нужных местах, особенно в части предложения "а также". Пожалуйста, предоставьте правильную запись в новом дополнительном (или заменяющем) комментарии, и я могу разбирать то, что вы на самом деле напечатали, а не то, что, как я предполагаю, вы напечатали.

Значение a[i] + j должно дать вам указатель на целое число j th в строке i th данных. Вы должны были бы обернуть это как *(a[i] + j), чтобы получить значение int. Версия в скобках также дает указатель на элемент a[i][j]; вам нужно * впереди, чтобы получить значение.

Варианты типа *(a+(i*cols)+j) неправильны, потому что в этом контексте вам не нужно умножать количество столбцов. Нет необходимости, чтобы последовательные строки, на которые указывают a[i] и a[i+1], были непрерывными в памяти; они могут быть расположены в нескольких непересекающихся блоках памяти (и фактически может быть несколько указателей на один блок памяти). Конечно, внутри ряда элементы должны быть смежными.

Это примерно разница между int a[][4]:

+---+---+---+---+
| 2 | 3 | 5 | 7 |
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
| 7 | 5 | 3 | 2 |
+---+---+---+---+
| 9 | 8 | 7 | 6 |
+---+---+---+---+

и int *a[]:

+------------+       +---+---+---+---+
| 0x010C0304 |------>| 2 | 3 | 5 | 7 |
+------------+       +---+---+---+---+
| 0x020D0408 |------>| 1 | 2 | 3 | 4 |
+------------+       +---+---+---+---+
| 0x030E050C |------>| 7 | 5 | 3 | 2 |
+------------+       +---+---+---+---+
| 0x040F0600 |------>| 9 | 8 | 7 | 6 |
+------------+       +---+---+---+---+

Обратите внимание, что для хранения данных int *a[] требуется больше памяти, чем int a[][4]. Также обратите внимание, что я сделал 4 массива чисел в int *a[] несмежными. Кроме того, строки в примере int *a[] могут иметь различную длину, при условии, что вы знаете, как получить доступ к длине (или все длины по крайней мере 4, и вы не выходите за пределы четвертого элемента).

Адаптация MCVE из вопроса

Как я уже говорил в предыдущем разделе, когда вы используете нотацию «массив указателей», использование cols в расчете нижнего индекса означает неправильно . Вот адаптация вашего кода - мне пришлось удалить специфичные для Windows функции, такие как _getch() и scanf_s() (используя вместо этого «ничто» и scanf()). Я также показал некоторые альтернативы; печать показывает некоторые альтернативы.

#include <stdio.h>
#include <stdlib.h>

#define MAX_ROWS 20

extern void modify(int rows, int cols, int *a[]);  
extern void input_2d_array(int rows, int cols, int *a[]); 
extern void output_2d_array(int rows, int cols, int *a[]);


int main(void)
{
    int rows = 4, cols = 3;

    int *a[MAX_ROWS], i;

    for (i = 0; i < rows; i++)
        a[i] = (int *)malloc(cols * sizeof(int));

    input_2d_array(rows, cols, a);

    printf("Initially in main array =\n");
    output_2d_array(rows, cols, a);

    modify(rows, cols, a);

    printf("Finally in main array =\n");
    output_2d_array(rows, cols, a);

    //_getch();
    return 0;
}

void input_2d_array(int rows, int cols, int *a[])
{
    int i, j;

    for (i = 0; i < rows; i++)
    {
        for (j = 0; j < cols; j++)
        {
            printf("Please enter the elements for row %d and column %d of a\n", i + 1, j + 1);
            //scanf_s(" %d", (a + (i * cols) + j));
            //scanf(" %d", (a + (i * cols) + j));
            scanf(" %d", &a[i][j]);
            //scanf(" %d", a[i] + j);
            //scanf(" %d", *(a + i) + j);
        }
    }
}

void output_2d_array(int rows, int cols, int *a[])
{
    int i, j;

    for (i = 0; i < rows; i++)
    {
        for (j = 0; j < cols; j++)
        {
            //printf(" %d", *(a + (i * cols) + j));
            printf(" %d", a[i][j]);
            printf(" (%d)", *(*(a + i) + j));
        }
        printf("\n");
    }
}

void modify(int rows, int cols, int *a[])
{
    int i, j;

    printf("Initally in modify array =\n");
    output_2d_array(rows, cols, a);

    for (i = 0; i < rows; i++)
    {
        for (j = 0; j < cols; j++)
        {
            a[i][j] = a[i][j] + 2;
            *(a[i] + j) = *(*(a + i) + j) + 2;
            //*(a + (i * cols) + j) = (*(a + (i * cols) + j)) + 2;
            // *(*(a + i) + j) += 2; // Gives exception
            // *(a[i]+j) += 10; // Gives exception
        }
    }

    printf("Finally in modify array =\n");
    output_2d_array(rows, cols, a);
}

Пример выполнения (программа mda17):

$  mda17
Please enter the elements for row 1 and column 1 of a
23
Please enter the elements for row 1 and column 2 of a
24
Please enter the elements for row 1 and column 3 of a
25
Please enter the elements for row 2 and column 1 of a
26
Please enter the elements for row 2 and column 2 of a
27
Please enter the elements for row 2 and column 3 of a
28
Please enter the elements for row 3 and column 1 of a
99
Please enter the elements for row 3 and column 2 of a
98
Please enter the elements for row 3 and column 3 of a
97
Please enter the elements for row 4 and column 1 of a
96
Please enter the elements for row 4 and column 2 of a
95
Please enter the elements for row 4 and column 3 of a
94
Initially in main array =
 23 (23) 24 (24) 25 (25)
 26 (26) 27 (27) 28 (28)
 99 (99) 98 (98) 97 (97)
 96 (96) 95 (95) 94 (94)
Initally in modify array =
 23 (23) 24 (24) 25 (25)
 26 (26) 27 (27) 28 (28)
 99 (99) 98 (98) 97 (97)
 96 (96) 95 (95) 94 (94)
Finally in modify array =
 27 (27) 28 (28) 29 (29)
 30 (30) 31 (31) 32 (32)
 103 (103) 102 (102) 101 (101)
 100 (100) 99 (99) 98 (98)
Finally in main array =
 27 (27) 28 (28) 29 (29)
 30 (30) 31 (31) 32 (32)
 103 (103) 102 (102) 101 (101)
 100 (100) 99 (99) 98 (98)
$
0 голосов
/ 15 сентября 2018

Чтобы не использовать указатели - оберните массив в структуру. Затем вся структура (включая массив) будет передана функции.

0 голосов
/ 15 сентября 2018

Проверьте это и попробуйте найти, передан ли массив как массив или как указатель на некоторую функцию:

 char arr[]={'a','b','c'};
 int sizeOfArr = sizeof(arr)/sizeof(arr[0]);

в обеих функциях найти размер массива, используя метод, описанный выше, где было создано arr[]и где он был пройден - sizeOfArr расшифрует вещи.Возможно, из этого будет понятно, почему размер / размеры были переданы явно - это все, что я могу сделать из всего вашего утверждения.Поправь меня, если я ошибаюсь.

...