Так же, как и другие переменные, массивы могут быть переданы в функции в виде аргументов. Рассмотрим такую программу:
#include <stdio.h>
#include <time.h>
#define N 10
void arr_make(int arr[], int min, int max);
int main () {
int arrI[N], i;
arr_make(arrI, 30, 90);
for (i=0; i<N; i++)
printf("%d ", arrI[i]);
printf("\n");
}
void arr_make(int arr[], int min, int max) {
int i;
srand(time(NULL));
for (i=0; i<N; i++)
arr[i] = rand() % (max - min + 1) + min;
}
Внутри функции main()
создается массив из 10 элементов. Затем вызывается функция arr_make()
,
в которую передаются имя массива и два целых числа как аргументы.
При взгляде на функцию arr_make()
можно заметить, что ее первый параметр выглядит необычно. Она принимает массив
неизвестного размера. Если предположить, что массивы передаются по значению, то есть копируются, как компилятор определит необходимую
память для arr_make()
, не зная точного размера ее параметра?
Ранее мы узнали, что имя массива является указывающим на первый элемент константным указателем, т.е. содержит адрес. По сути, мы передаем в функцию копию адреса, а не копию самих значений. Это позволяет редактировать локальные переменные в вызывающей функции из внешней. Несколько переменных могут ссылаться на одну и ту же память, и изменение значения через одну из них приведет к изменению значений всех связанных переменных.
Обозначение arr[]
в параметрах функций означает, что фактически передается указатель на массив, а не скалярная
переменная типа int, char, float и подобных.
Разберем это далее. Если функция принимает только адрес массива, внутренняя структура массива не существует, и выражение типа arr[i]
внутри функции использует arr как указатель с добавлением смещения. Поэтому внутри функции arr_make()
цикл
можно выразить как:
for(i=0; i<N; i++)
*arr++ = rand() % (max - min + 1) + min;
В цикле происходит запись результата выражения справа от знака равно в адрес, на который указывает arr. Это обеспечивает
выражение *arr
. Затем указатель arr сдвигается к следующей ячейке памяти прибавлением единицы (arr++
).
Сначала происходит запись по адресу, хранимому в arr, после чего меняется сам адрес, сдвигаясь на одну ячейку памяти заданного
размера.
Поскольку мы меняем arr, это доказывает, что оно является обычным указателем, а не именем массива. Тогда зачем в заголовке
функции такой вид arr[]
? В большинстве случаев предпочитается использовать просто переменную-указатель:
void arr_make(int *arr, int min, int max);
Хотя в этом случае сразу не ясно, принимает ли функция указатель на одиночную переменную или же на массив. В любом случае она будет работать корректно.
При передаче массивов в функцию часто также передают количество его элементов в виде отдельного параметра. В примере выше
N
является глобальной константой, поэтому доступна как из функции main()
, так и arr_make()
.
Более корректно было бы изменить функцию arr_make()
следующим образом:
void arr_make(int *arr, int n, int min, int max) {
int i;
srand(time(NULL));
for (i=0; i<n; i++)
arr[i] = rand() % (max - min + 1) + min;
}
Параметр n определяет число элементов в массиве, которые будут обрабатываться.
Стоит отметить, что передача имени массива в функцию позволяет последней модифицировать его. Но такой эффект нужен не всегда. Можно просто не менять данные в массиве из функции, например, при подсчете суммы элементов множества; сами элементы тогда неизменяемы:
int arr_sum(int *arr) {
int i, s=0;
for(i=0; i<N; i++) {
s = s + arr[i];
}
return s;
}
Однако, если необходимо создать более надежную программу, в которой большинство функций не должны менять данные массива, стоит объявить параметр-указатель как константу, например:
int arr_sum(const int *arr);
Таким образом, любые попытки изменения значения через такой указатель приведут к ошибке, и программист будет уведомлен, что функция пытается поменять содержимое массива.
Модернизируем программу, приведенную в начале урока:
#include <stdio.h>
#include <time.h>
#define N 10
void arr_make(int *arr, int min, int max);
void arr_inc_dec(int arr[], char sign);
void arr_print(int *arr);
int main () {
int arrI[N], i, minimum, maximum;
char ch;
printf("Enter minimum & maximum: ");
scanf("%d %d", &minimum, &maximum);
arr_make(arrI, minimum, maximum);
arr_print(arrI);
scanf("%*c"); // избавляемся от \n
printf("Enter sign (+,-): ");
scanf("%c", &ch);
arr_inc_dec(arrI, ch);
arr_print(arrI);
}
void arr_make(int *arr, int min, int max) {
int i;
srand(time(NULL));
for(i=0; i<N; i++)
*arr++ = rand() % (max - min + 1) + min;
}
void arr_inc_dec(int *arr, char sign) {
int i;
for (i=0; i<N; i++) {
if (sign == '+') arr[i]++;
if (sign == '-') arr[i]--;
}
}
void arr_print(int *arr) {
int i;
printf("The array is: ");
for (i=0; i<N; i++)
printf("%d ", *arr++);
printf("\n");
}
Теперь пользователю предлагается ввести минимальное и максимальное значения, после чего создается массив с элементами в этом диапазоне.
Массив выводится на экран функцией arr_print()
. Далее пользователь вводит символ + или -, и в зависимости от этого
выбора функция arr_inc_dec()
увеличивает или уменьшает на единицу значения элементов массива.
В функциях arr_make()
и arr_print()
используется указательная нотация, и значения указателей
изменяются: сначала они указывают на первый элемент, затем на второй и так далее. В функции arr_inc_dec()
реализовано
обращение к элементам массива. При этом сам указатель остается неизменным: к arr добавляется смещение, увеличивающееся на каждой
итерации цикла. Отметим, что выражение arr[i]
представляет собой *(arr+i)
.
Использование нотации обращения к элементам массива делает программы более читаемыми, а запись с помощью указателей позволяет компилировать
их немного быстрее, так как при встрече выражения arr[i]
компилятор выполняет дополнительную операцию преобразования
в *(arr+i)
. Однако, целесообразнее сделать код более понятным и потерять немного времени при компиляции.
- Переделайте программу, чтобы она работала с вещественными числами. Вместо функции
arr_inc_dec()
создайте другую, изменяющую значения элементов массива на любое указанное пользователем число. - Напишите программу, в которой из одной функции в другую передается указатель не на начало массива, а на его среднюю часть.
- Создайте программу, где из функции
main()
в иную функцию передаются два массива: один заполненный, другой пустой. Во второй функции элементам пустого массива должны присваиваться преобразованные элементы заполненного массива, который модулировать нельзя.