Передача массивов в функции
Сначала вспомним обычную переменную
Обычная переменная (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— результат остаётся в массиве.