Как передать одномерный массив в функцию c
Перейти к содержимому

Как передать одномерный массив в функцию c

  • автор:

Как передать одномерный массив в функцию c

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

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

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

using namespace std;

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

void print(int x[], int n)

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

в неё передаётся адрес начала массива x и количество элементов n . Количество элементов 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] позволяет при необходимости передать в подпрограмму не весь массив, а только его часть, начиная с какого-либо адреса. Например, вызов

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

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

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

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

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

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

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

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

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

using namespace std;

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

Как передать одномерный массив в функцию c

Если функция принимает в качестве параметра массив, то фактически в эту функцию передается указатель на первый элемент массива. То есть как и в случае с указателями нам доступен адрес, по которому мы можем менять значения. Поэтому следующие объявления функции будут по сути равноценны:

void print(int numbers[]); void print(int *numbers);

Передадим в функцию массив:

#include void print(int[]); int main() < int nums[] ; print(nums); > void print(int numbers[]) < std::cout 

В данном случае функция print выводит на консоль первый элемент массива.

Теперь определим параметр как указатель:

#include void print(int*); int main() < int nums[] ; print(nums); > void print(int *numbers)

Здесь также в функцию передается массив, однако параметр представляет указатель на первый элемент массива.

Ограничения

Поскольку параметр, определенный как массив, рассматривается именно как указатель на первый элемент, то мы не сможем корректно получить длину массива, например, следующим образом:

void print(int numbers[]) < int size = sizeof(numbers) / sizeof(numbers[0]); // или так // size_t size = std::size(nums); std::cout

И также мы не сможем использовать цикл for для перебора этого массива:

void print(int numbers[])

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

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

Первый подход заключается в том, чтобы один из элементов массива сам сигнализировал о его окончании. В частности, массив символов может представлять строку - набор символов, который завершается нулевым символом '\0'. Фактически нулевой символ служит признком окончания символьного массива:

#include void print(char[]); int main() < char chars[] ; print(chars); > void print(char chars[]) < for (unsigned i<>; chars[i] != '\0'; i++) < std::cout >

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

#include void print(int[], size_t); int main() < int nums[]; size_t n ; print(nums, n); > void print(int numbers[], size_t n) < for(size_t i <>; i < n; i++) < std::cout >

Третий подход заключается в передаче указателя на конец массива. Можно вручную вычислять указатель на конец массива. А можно использовать встроенные библиотечные функции std::begin() и std::end() :

int nums[] < 1, 2, 3, 4, 5 >; int *begin ; // указатель на начало массива int *end ; // указатель на конец массива

Причем end возвращает указатель не на последний элемент, а адрес за последним элементом в массиве.

Применим данные функции:

#include void print(int*, int*); int main() < int nums[] < 1, 2, 3, 4, 5 >; int *begin ; int *end ; print(begin, end); > void print(int *begin, int *end) < for (int *ptr ; ptr != end; ptr++) < std::cout >

Константные массивы

Поскольку при передаче массива передается фактически указатель на первый элемент, то используя этот указатель, мы можем изменить элемены массива. Если нет необходимости в изменении массива, то лучше параметр-массив определять как константный:

#include void print(const int*, const size_t); void twice(int*, const size_t); int main() < int numbers[]; size_t n = std::size(numbers); print(numbers, n); twice(numbers, n); // увеличиваем элементы массива в два раза print(numbers, n); > void print(const int numbers[], const size_t n) < for(size_t i <>; i < n; i++) < std::cout std::cout void twice(int *numbers, const size_t n) < for(size_t i <>; i < n; i++) < numbers[i] = numbers[i] * 2; >>

В данном случае функция print просто выводит значения из массива, поэтому параметры этой функции помечаются как константные.

Функция twice изменяет элементы массива - увеличивает их в два раза, поэтому в этой функции параметр-массив является неконстантным. Причем поле выполнения функции twice массив numbers будет изменен.

Консольный вывод программы:

1 2 3 4 5 2 4 6 8 10

Передача массив по ссылке

Еще один сценарий передачи массива в функцию представляет передача массива по ссылке. Прототип функции, которая принимает массив по ссылке, выглядит следующим образом:

void print(int (&)[]);

Обратите внимание на скобки в записи (&) . Они указывают именно на то, что массив передается по ссылке. Пример использования:

#include void print(int (&)[], size_t); int main() < int nums[] ; size_t count = std::size(nums); print(nums, count); > void print(int (&numbers)[], size_t count) < for(size_t i<>; i < count; i++) < std::cout >

Подобным образом можпо передавать константные ссылки на массивы.

void print(const int (&)[]);

С одной стороны, может показаться, что в передаче массива по ссылке нет большого смысла, поскольку при передачи массива по значению итак просто передается адрес этого массива. Но с другой стороны, передача массива по ссылке имеет некоторые преимущества. Во-первых, не копируется значение - адрес массива, мы напрямую работаем с оригинальным массивом. Во-вторых, передача массива по ссылке позволяет ограничить размер такого массива, соотвественно при компиляции компилятор уже будет знать, сколько элементов будет иметь массив.

#include void print(const int (&)[5]); // массив строго с 5 элементами int main() < int nums1[] ; print(nums1); > void print(const int (&numbers)[5]) < for(unsigned i<>; i < 5; i++) < std::cout >

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

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

int nums2[] ; print(nums2); // ! Ошибка - в массиве nums2 6 элементов

Передача многомерного массива

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

Когда определяется параметр как указатель на массив, размер второй размерности (а также всех последующих размерностей) должен быть определен, так как данный размер является частью типа элемента. Пример объявления:

void print(int (*numbers)[3]);

Здесь предполагается, что передаваемый массив будет двухмерным, и все его подмассивы будут иметь по 3 элемента. Стоит обратить внимание на скобки вокруг имени параметра, которые и позволяют определить параметр как указатель на массив. И от этой ситуации стоит отличать следующую:

void print(int *numbers[3])

В данном случае параметр определен как массив указателей, а не как указатель на массив.

Рассмотрим применение указателя на массив в качестве параметра:

#include void print(const int(*)[3], const size_t); int main() < int table[][3] < , , >; // количество строк или подмассивов size_t rowsCount ; print(table, rowsCount); > void print(const int (*rows)[3], const size_t rowsCount) < // количество столбцов или элементов в каждом подмассиве size_t columnsCount ; for(size_t i<>; i < rowsCount; i++) < for (size_t j<>; j < columnsCount; j++) < std::cout std::cout >

В функции main определяется двухмерный массив - он состоит из трех подмассивов. Каждый подмассив имеет по три элемента.

В функцию print вместе с массивом передается и число строк - по сути число подмассивов. В самой функции print получаем количество элементов в каждом подмассиве и с помощью двух циклов перебираем все элементы. С помощью выражения rows[0] можно обратиться к первому подмассиву в двухмерном массиве, а с помощью выражения rows[0][0] - к первому элементу первого подмассива. И таким образом, манипулируя индексами можно перебрать весь двухмерный массив.

В итоге мы получим следующий консольный вывод:

1 2 3 4 5 6 7 8 9

Также мы могли бы использовать нотацию массивов при объявлении и определении функции print, которая может показаться проще, нежели нотация указателей. Но в этом случае опять же надо было бы указать явным образом вторую размерность:

#include void print(const int[][3], const size_t); int main() < int table[][3] < , , >; // количество строк или подмассивов size_t rowsCount ; print(table, rowsCount); > void print(const int rows[][3], const size_t rowsCount) < // количество столбцов или элементов в каждом подмассиве size_t columnsCount ; for( size_t i<>; i < rowsCount; i++) < for (size_t j<>; j < columnsCount; j++) < std::cout std::cout >

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

Здесь рассмотрена операция по передаче массивов в качестве аргументов функции, поскольку существуют исключения из стандартного правила передачи по значению.

Когда массив используется в качестве аргумента функции, передается только адрес массива, а не копия всего массива. При вызове функции с именем массива в функцию передается указатель на первый элемент массива. (Надо помнить, что в С имена массивов без индекса - это указатели на первый элемент массива.) Параметр должен иметь тип, совместимый с указателем. Имеется три способа объявления параметра, предназначенного для получения указателя на массив. Во-первых, он может быть объявлен как массив, как показано ниже:

#include
void display(int num[10]);
int main (void) /* вывод чисел */
int t [10], i;
for (i=0; i display(t);
return 0;
>

Хотя параметр num объявляется как целочисленный массив из десяти элементов, С автоматически преобразует его к целочисленному указателю, поскольку не существует параметра, который мог бы на самом деле принять весь массив. Передается только указатель на массив, поэтому должен быть параметр, способный принять его.

Следующий способ состоит в объявлении параметра для указания на безразмерный массив, как показано ниже:

где num объявлен как целочисленный массив неизвестного размера. Поскольку С не предоставляет проверку границ массива, настоящий размер массива не имеет никакого отношения к параметру (но, естественно, не к программе). Данный метод объявления также определяет num как целочисленный указатель.

Последний способ, которым может быть объявлен num, - это наиболее типичный способ, применяемый при написании профессиональных программ, - через указатель, как показано ниже:

Он допустим, поскольку любой указатель может быть индексирован с использованием [], если он является массивом. (На самом деле массивы и указатели очень тесно связаны друг с другом.) Все три метода объявления параметра приводят к одинаковому результату - указателю. С другой стороны, элемент массива используется как аргумент, трактуемый как и другие простые переменные. Например, программа может быть написана без передачи всего массива:

void display(int num)
printf ("%d ", num);
>

Как можно видеть, в display() передается параметр типа int. Не имеет значения, что display() вызывается с элементом массива в качестве параметра, поскольку передается только одно значение.

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

#include
#include
void print_upper(char *string);
int main(void) /* вывод строки в верхнем регистре */
char s[80];
gets (s);
print_upper(s) ;
return 0;
>

void print_upper(char *string)
register int t;
for(t=0; string[t]; ++t)
string[t] = toupper(string[t]);
printf("%c", string[t]);
>
>

После вызова print upper() происходит изменение содержимого массива s в main(). Если это не нужно, следует переписать программу следующим образом:

# include
#include
void print upper(char *string);
int main(void) /* вывод строки в верхнем регистре */
char s[80];
gets(s);
print_upper(s);
return 0;
>

void print_upper(char *string)
register int t;
for(t=0; string[t]; ++t)
printf ("%c", toupper (string[t]));
>

В данной версии содержимое массива s остается неизменным, поскольку значения не меняются.

Классический пример передачи массивов в функции находится в стандартной библиотечной функции gets(). Хотя gets() из библиотеки Borland С++ гораздо сложнее, функция, показанная в данном примере, содержит основную идею работы. Для того, чтобы избежать путаницы и не вызвать стандартную функцию, данная функция называется xgets().

/* простейшая версия стандартной библиотечной функции gets() */

void xgets (char *s)
register char ch;
register int t;
for(t=0; t ch = getche();
switch(ch)
case ' \r':
s[t] = '\0'; /* null завершает строку */
return;
case '\b':
if(t>0) t-;
break;
default:
s[t] = ch;
t++;
>
>
s[79] = ' \0';
>

Функция xgets() должна вызываться с указателем на символ. Это может быть имя символьного массива, который по определению является указателем на символ. xgets() организует цикл for от 0 до 79. Таким образом предотвращается ввод больших строк с клавиатуры. Если набирается более 80 символов, функция завершает работу. Поскольку C не имеет проверки границ массива, следует убедиться, что массив, передаваемый в xgets(), может принять, по крайней мере, 80 символов. По мере набора символов на клавиатуре они вводятся в строку. Если набирается забой, счетчик t уменьшится на 1. При нажатии на ввод помещается нулевой символ в конец строки, то есть строка оканчивается. Поскольку массив, используемый при вызове xgets(), модифицируется, после возврата он будет содержать набранные символы.

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

При передаче одномерных массивов в функции следует вызывать функцию с именем массива без индекса. В результате этого передается адрес первого элемента массива. В С невозможно передать весь массив как аргумент. Вместо этого автоматически передается указатель. Следующий пример передает адрес i в func1();

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

void fun1(int a[10]) /* массив с фиксированной длиной */
.
>

void fun1(int a []) /* безразмерный массив */
.
>

Все три способа объявления сообщают компилятору, что предполагается получение указателя на целое, В первом объявлении используется указатель. Во втором - стандартный способ объявления массива. В третьем - модифицированная версия объявления массива, указывающая на то, что предполагается получение массива типа int некоторой длины. Не имеет значения длина передаваемого массива, поскольку С не выполняет проверку выхода за границы массива. Фактически

также будет работать, поскольку компилятор создает код, указывающий func1() о получении указателя (на самом деле не создается 32-элементный массив).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *