Массивы как параметры функций

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

Использование массива как параметра функции не вызывает ни каких трудностей. Как было сказано ранее, массив в подпрограмму всегда передаётся по адресу, поэтому достаточно при вызове функции указать адрес начала массива или адрес того элемента, начиная с которого предполагается обрабатывать массив.

Пример. Написать программу, которая будет вызывать функцию для вывода на печать элементов массива.

Возможный вариант решения:

#include <iostream>

using namespace std;

void print(int x[], int n);

int main()

{

   const int n = 5;

   int x[n] = {3, 5, 1 ,7, 4};

   print(x, n);

   return 0;

}

void print(int x[], int n)

{

   cout << "Massiv:" << endl;

   for(int i = 0; i < n; i++)

      cout << x[i] << endl;

}

При вызове функции оператором

print(x, n);

в неё передаётся адрес начала массива x и количество элементов n. Количество элементов n передаётся по значению и здесь всё должно быть понятно, но вот с передачей самого массива может быть некоторое непонимание, которое необходимо устранить.

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

print(&x[0], n);

С учётом того, что массив в подпрограмму чаще всего передаётся весь и с начала, разработчики языка запись вида &x[0] сократили до x, т.е. обращение к массиву по имени эквивалентно обращению по адресу к элементу с индексом 0.

В заголовке функции

void print(int x[], int n) // 1-й вариант

два формальных параметра. Запись x[] говорит о том, что в подпрограмму передаётся именно массив.

Заголовок может выглядеть и так:

void print(int *x, int n) // 2-й вариант

Массив передаётся по адресу, поэтому и записываем первый параметр как адрес на объект типа int. Этот объект-указатель принимает адрес того элемента массива, который вычисляется в вызывающей функции (например, в main()).

Какую форму использовать? Это дело вкуса. Компилятор рассматривает 1-й вариант как эквивалент 2-го, хотя для человека нагляднее именно 1-й вариант. По нему видно, что формальный параметр — это именно массив, а не указатель, например, на одиночный элемент.

Трактовка при вызове x как &x[0] позволяет при необходимости передать в подпрограмму не весь массив, а только его часть, начиная с какого-либо адреса. Например, вызов

print(&x[2], n - 2);

приводит к выводу на экран монитора трёх элементов массива, начиная с элемента с индексом 2. При этом сама функция не требует каких либо доработок. В этом плане языки С/С++ гораздо удобнее многих других языков. Так, в Паскале или Бейсике пришлось бы передавать в подпрограмму ещё один параметр — номер элемента, с которого необходимо начать обработку массива, а затем этот номер использовать в операторе цикла.

Матрица как параметр функции

Передача в подпрограмму матрицы в качестве параметра несколько сложнее, чем передача одномерного массива.

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

Тип_данных Имя[][]

К сожалению, это работать не будет. Первая пара пустых квадратных скобок указывает на то, что имеем дело с массивом. Это допустимо. Вторая пара квадратных скобок как признак того, что это тоже массив, сгенерирует ошибку. Здесь компилятор захочет больше конкретики. Массив из неизвестно каких массивов его не устроит. Во вторых скобках надо записать константу, чтобы компилятор мог понять, как память в матрице распределяется по строкам и столбцам.

Таким образом, допустима запись

Тип_данных Имя[][Константа]

Рассмотрим небольшой пример, поясняющий сказанное выше.

Пример. Написать и протестировать функцию вывода значений целочисленной матрицы на экран монитора.

Возможный вариант реализации:

#include <iostream>

using namespace std;

#define M 3

void PrintMatr(int a[][M], int n, int m);

int main()

{

   const int n=2;

   const int m=M;

   int a[n][m] = {{5, 4, -1}, {2, 6, 7}};

   PrintMatr(a, n, m);

   return 0;

}

// Вывод матрицы на экран монитора

void PrintMatr(int a[][M], int n, int m)

{

   int i, j;

   cout << "Matriza:" << endl;

   for(i = 0; i < n; i++)

   {

      for(j = 0; j < m; j++)

         cout << a[i][j] << " ";

      cout << endl;

   }

}

Удобна такая передача матрицы в подпрограмму? В общем случае нет. Это приемлемо только для небольших программ с матрицами одинакового размера. Для нескольких матриц разного размера можно дать предельное количество строк (M) по максимуму. Но что делать, если появится желание или необходимость откомпилировать функцию и включить её объектный модуль в библиотеку? Глобальное имя (M) в этом случае совершенно не приемлемо.

Хорошим решением может стать использование динамического массива. В этом случае в подпрограмму будет передаваться указатель на указатель. Ни каких глобальных констант не потребуется.

Приведём пример программы с использованием динамической матрицы и подпрограмм, в которые эта матрица передаётся.

Пример. Дана прямоугольная матрица. Преобразовать матрицу по правилу: переставляем местами первый столбец и последний, затем — второй и предпоследний, и т.д.

Возможный вариант реализации:

#include <iostream>

using namespace std;

void ReadMatr(int **a, int n, int m);

void PrintMatr(int **a, int n, int m);

void P(int **a, int n, int m);

int main()

{

   int n=3, m=4, i;

   int **a;

   a = new int*[n];

   for(i = 0; i < n; i++)

      a[i] = new int [m];

   ReadMatr(a, n, m);

   P(a, n, m);

   PrintMatr(a, n, m);

   for(i = 0; i < n; i++)

      delete []a[i];

   delete []a;

   return 0;

}


// Ввод матрицы с клавиатуры

void ReadMatr(int **a, int n, int m)

{

   int i, j;

   cout << "Input matriza A(" << n << "*" << m << "):" << endl;

   for(i = 0; i < n; i++)

      for(j = 0; j < m; j++)

         cin >>a[i][j];

}


// Вывод матрицы на экран монитора

void PrintMatr(int **a, int n, int m)

{

   int i, j;

   cout << "Matriza:" << endl;

   for(i = 0; i < n; i++)

   {

      for(j = 0; j < m; j++)

         cout << a[i][j] << " ";

      cout << endl;

   }

}


// Перестановка столбцов

void P(int **a, int n, int m)

{

   int i, j, j1, c;

   for(i = 0; i < n; i++)

      for(j = 0, j1 = m - 1; j < j1; j++, j1--)

      {

         c = a[i][j];

         a[i][j] = a[i][j1];

         a[i][j1] = c;

      }

}

Теперь нашим функциям не требуются ни какие глобальные данные, они не имеют ограничений на использование.

Недостатком использования в качестве параметров динамических массивов (любых: одномерных или многомерных) является снижение скорости выполнения программы, т.к. дополнительно требуется время на выделение и освобождение динамической памяти под массивы. К тому же, необходимо не забывать освобождать динамически выделяемую память. Такая забывчивость в программах на С/С++ не является редкостью, а скорее относится к типовым ошибкам программирования, характерным при работе именно на этих языках.

Многомерные массивы как параметры функций

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

// Прототип функции печати трёхмерного массива

void Print(int V[][N][M], int k, int n, int m);

Здесь необходимы уже две константы: N и M. Недостатки такой функции те же, что и при передачи в подпрограмму матрицы, т.е. отсутствие универсальности.

Решение проблемы — так же в использовании динамических массивов:

// Прототип функции печати трёхмерного динамического массива

void Print(int ***V, int k, int n, int m);

Универсальность достигнута, но в ущерб скорости работы программы.



Назад
На верх
Вперёд
Hosted by uCoz