Передача массива в качестве параметра функции
Рассмотренное понятие указателя усложняется ещё и тем, что для массивов указатели имеют некоторые особенности. Пусть мы объявили статический массив, то есть массив с фиксированной размерностью: const n=10; int A[n]; Этим самым на этапе компиляции выделяется непрерывный участок памяти из 4*n =40 байт для размещения n целых чисел. Кроме этого, без какого-либо дополнительного объявления, без использования символа “*” мы объявили и переменную-указатель с именем массива A, в которой находится адрес начала массива, т. е. номер первого байта элемента A[0]. Этот адрес можно в программе обозначить ещё и как &A[0].
Идентификатор массива определяется не просто как указатель, а как константный указатель на первый элемент массива. Это означает, что нельзя изменить адрес начала массива, то есть нельзя, например, переменной А присвоить значение другого адреса. Если int *q; то запись A=q;недопустима. Но обратное присваивание q=A; или q=&A[0]; разрешено. После такого присваивания элементы одного и того же массива можно обозначать не только A[i], но и q[i], где i = 0, 1, 2, …, n-1. Более того, переменной указателю q можно присвоить адрес любого, не обязательно первого с нулевым номером элемента массива. Например, после присваивания q=&A[5]; q[0] и A[5] — это одна и таже ячейка, один и тот же элемент массива с разными именами, q[1] — это A[6], и т. д., q[4] — это A[9].
Сказанное выше используется при передаче массива в качестве параметра функции. Независимо от того, является массив входным, выходным, или и тем и другим, то есть преобразуется, он передаётся одинаково в качестве параметра функции с помощью указателей по следующим правилам.
Заголовок функции можно записать по-разному. Например, функцию сортировки одномерного массива можно объявить двумя способами:
a) void MySort(int x[], int size); или b) void MySort(int *x, int size);
В заголовке функции указываем тип элементов массива, его имя и пустые квадратные скобки или указатель на массив, что одно и то же. Более того, в прототипе можем записать int x[], а в заголовке при определении этой же функции — int * x, или наоборот. Чаще всего в качестве параметра указывается и его размерность (size в примере). Этим самым мы подчёркиваем, что работаем с массивом произвольной размерности. Отметим, что из заголовка ещё не видно, что x — адрес массива, а не простой переменной.
В тексте функции, как обычно, доступ к элементам массива осуществляется с помощью индексов. Кроме этого, во второй части книги будет рассмотрен другой способ доступа к элементам массива, основанный на явном использовании указателей и операций для работы с ними.
Вызов такой функции выполняется одинаково и не зависит от вариантов прототипа (a) или b)). В вызывающей функции, например, в main, объявляем статический массив обычным образом const n=10; int A[n]; При вызове функции в качестве фактического параметра надо записать адрес начала массива, если обрабатываем его с самого начала. Поэтому в скобках в таком случае записываем только имя массива A или адрес его первого элемента &A[0], что одно и то же: MySort(A,n); или MySort(&A[0],n); Будет ошибка, если при вызове функции в качестве фактического параметра запишем, например, A[i], или A[n] (так записываем при объявлении массива), или A[] (так записываем при определении формального, а не фактического параметра). То есть следующие вызовы компилятор не пропустит:
MySort(A[i],n); — ошибка, так как A[i] при i=0, 1, ... , n-1— это число или элемент массива, а не адрес;
MySort(A[n],n); — ошибка, так как A[n] это не адрес и, более того, n превосходит наибольшее значение индекса, так как нумерация элементов массива с нуля;
MySort(A[],n); — неправильно;
MySort(*A, n); — тоже неправильно;
При использовании массива в качестве параметра функции фактически передаётся не сам массив, а указательна него, или адрес массива, то есть номер первого байта первой ячейки массива (с индексом 0). Другими словами, int x[] (или int *x) в заголовке функции означает, что ячейка x предназначена для хранения адреса массива. При вызове функции из объявления int A[n] следует, что A, или &A[0], — это адрес начала массива из n элементов. Как и для простых переменных, этот адрес (но не сам массив) копируется в ячейку x. В результате получается, что в Aи в xхранятся адреса одной и той же области оперативной памяти. Другими словами, массив в памяти не дублируется, а его элементы по-разному называются: A[0] в main — это та же ячейка, что и x[0] в MySort, A[1] — это та же ячейка, что и x[1], и так далее. Поэтому если в функции элементы массива с именем x изменим, то этим самым мы изменим и элементы массива A. Поэтому с помощью указателя можно не только передать массив в функцию, но и вернуть из неё вновь полученный или изменённый массив.
Никакого ссылочного типа для возврата массива из функции не требуется.
Благодаря указателям составленную функцию для работы с одномерным массивом можно без изменений использовать для обработки части массива. Например, для сортировки n/2 первых элементов массива ту же функцию вызываем так: MySort (A, n/2); или MySort (&A[0], n/2); Заметим, что если n нечётное (например, n=15), то рассортируем первые n/2=7 элементов массива, то есть меньше половины.
Пусть надо рассортировать массив не с самого начала, а, например, с его середины и до конца. Тогда в качестве фактического параметра при вызове такой функции указываем адрес элемента a[n/2], а количество обрабатываемых элементов уменьшаем: MySort (&a[n/2], n%2 ? n/2+1 : n/2); Если n — нечётное (n=15), то, начав с a[n/2] (a[7]), мы рассортируем n/2+1=8 элементов, то есть больше половины. При чётном n n%2=0, что соответствует false, и рассортируем n/2 элементов.