Передача массивов в функции

Сначала вспомним обычную переменную

Обычная переменная (int, double, char) передаётся в функцию по значению: функция получает копию, а не саму переменную.

void inc(int x)
{
    x = x + 1;      /* меняется только копия */
}

int main(void)
{
    int a = 5;
    inc(a);
    printf("%d\n", a);   /* напечатает 5, а не 6 */
    return 0;
}

На пальцах: ты переписал число 5 на отдельный листок и отдал другу. Что бы друг с этим листком ни делал, у тебя в тетради число не изменится.

Массив копировать невыгодно

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

Массив передаётся по адресу (говорят «по ссылке»)

Функция получает не копию массива, а тот же самый массив, что и в main. Технически ей сообщают, где в памяти начинается ряд ячеек — адрес первой ячейки.

На пальцах. У тебя есть шкаф с пронумерованными ячейками. Ты не тащишь шкаф другу, а говоришь: «Шкаф стоит вон там, первая ячейка — вот эта, дальше по порядку». Друг приходит к тому же шкафу и берёт или кладёт вещи в те же ячейки. Никакая копия шкафа не создаётся.

Отсюда главное правило:

  • всё, что функция делает с элементами массива (a[i] = ...), видно в main;
  • массив не копируется — это один и тот же участок памяти.
void fill(int a[5])       /* функция работает с тем же массивом */
{
    a[0] = 100;           /* это изменение увидят в main */
}

int main(void)
{
    int x[5] = {1, 2, 3, 4, 5};
    fill(x);
    printf("%d\n", x[0]); /* напечатает 100 */
    return 0;
}

Пока указатели не изучали — достаточно запомнить: имя массива в вызове и есть его адрес (начало ряда). Поэтому перед именем массива не нужно писать &, а внутри функции это тот же массив, что и в main.

Как записать функцию для массива

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

void print_array(int a[], int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
}

Вызов — только имя массива, без & и без скобок:

print_array(x, 5);

Двумерный (матрица). Через запятую первым идёт число строк, вторым — число столбцов:

#define MAX_SIZE 20

void print_matrix(int a[MAX_SIZE][MAX_SIZE], int n, int m)
{
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            printf("%4d", a[i][j]);
        }
        printf("\n");
    }
}

Второй размер (MAX_SIZE) указывать обязательно, и он должен совпадать с тем, как матрица объявлена в main:

int a[MAX_SIZE][MAX_SIZE];                                 /* в main */
void print_matrix(int a[MAX_SIZE][MAX_SIZE], int n, int m); /* в функции — те же MAX_SIZE */

Первый размер можно опустить: void print_matrix(int a[][MAX_SIZE], int n, int m).

Трёхмерный. Все размеры, кроме первого, обязательны:

void print_cube(int a[MAX_SIZE][MAX_SIZE][MAX_SIZE], int layers, int rows, int cols)
{
    /* ... */
}

Вызов: print_cube(a, layers, rows, cols); — только имя массива, без & и без скобок.

Почему для матрицы нужен второй размер

Матрица хранится по строкам: сначала вся строка 0, потом вся строка 1 и так далее — как одна длинная полка. Чтобы по паре индексов a[i][j] найти нужную ячейку, нужно знать, сколько ячеек в одной строке.

На пальцах. В длинном ряду лежат ячейки, и мы знаем, что в каждой строке ровно MAX_SIZE штук. Тогда ячейка «строка 2, столбец 3» — это номер 2 * MAX_SIZE + 3. Если длину строки не знать, номер не посчитать. Поэтому второе число в квадратных скобках обязательно.

Зачем передавать n и m

Массив «не помнит», сколько в нём элементов: при передаче теряется информация о фактическом размере. Поэтому используемые размеры n и m всегда передаём отдельными параметрами и обходим матрицу по ним, а не по максимальным MAX_SIZE:

for (int i = 0; i < n; i++)        /* n — реальное число строк */
{
    for (int j = 0; j < m; j++)    /* m — реальное число столбцов */
    {
        /* ... */
    }
}

Программа из функций: пример

Ввод, вывод и обработка — в отдельных функциях, main только управляет порядком работы:

#include <stdio.h>

#define MAX_SIZE 20

void input_matrix(int a[MAX_SIZE][MAX_SIZE], int n, int m);
void print_matrix(int a[MAX_SIZE][MAX_SIZE], int n, int m);
int  sum_all(int a[MAX_SIZE][MAX_SIZE], int n, int m);

int main(void)
{
    int a[MAX_SIZE][MAX_SIZE];
    int n, m;

    printf("Введите N и M: ");
    if (scanf("%d %d", &n, &m) != 2 || n < 1 || m < 1 || n > MAX_SIZE || m > MAX_SIZE)
    {
        printf("Некорректные размеры\n");
        return 1;
    }

    input_matrix(a, n, m);          /* заполняет тот же массив a */
    printf("Исходная матрица:\n");
    print_matrix(a, n, m);

    printf("Сумма всех элементов: %d\n", sum_all(a, n, m));
    return 0;
}

void input_matrix(int a[MAX_SIZE][MAX_SIZE], int n, int m)
{
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            scanf("%d", &a[i][j]);
        }
    }
}

void print_matrix(int a[MAX_SIZE][MAX_SIZE], int n, int m)
{
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            printf("%4d", a[i][j]);
        }
        printf("\n");
    }
}

int sum_all(int a[MAX_SIZE][MAX_SIZE], int n, int m)
{
    int sum = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            sum += a[i][j];
        }
    }
    return sum;
}

Функция может изменить матрицу

Если функция меняет саму матрицу (транспонирование, обмен строк), она может ничего не возвращать — тип void. Изменения останутся в массиве и будут видны в main:

void swap_rows(int a[MAX_SIZE][MAX_SIZE], int m, int r1, int r2)
{
    for (int j = 0; j < m; j++)
    {
        int tmp = a[r1][j];
        a[r1][j] = a[r2][j];
        a[r2][j] = tmp;
    }
}

Вызов: swap_rows(a, m, 0, 2); — строки 0 и 2 действительно поменяются местами.

Частые ошибки

ОшибкаЧто не так
int a[][] в параметрахнельзя: для матрицы второй размер обязателен
размеры массива в main и в параметрах функции не совпадаютадресация ломается
забыли передать n и mобход идёт по «зашитым» MAX_SIZE и читает лишнее
в вызове написали print_matrix(a[MAX_SIZE][MAX_SIZE], n, m)нужно имя массива: print_matrix(a, n, m)
поставили & перед именем массиване нужно: имя массива уже обозначает его начало
ждут, что изменения массива «потеряются»массив передаётся по адресу, изменения сохраняются

Что запомнить

  • обычные переменные передаются копией; массив — по адресу (это тот же массив);
  • в вызове пишем только имя массива, без & и без скобок;
  • одномерный: void f(int a[], int n);
  • двумерный: void f(int a[MAX_SIZE][MAX_SIZE], int n, int m) — второй размер обязателен и совпадает с объявлением в main;
  • трёхмерный: void f(int a[MAX_SIZE][MAX_SIZE][MAX_SIZE], int l, int r, int c);
  • реальные размеры передаются отдельными параметрами: массив не хранит свою длину;
  • функции, которые меняют матрицу, часто имеют тип void — результат остаётся в массиве.

Подтверждение

—