Массивы в параметрах функции
Если функция принимает в качестве параметра массив, то фактически в эту функцию передается указатель на первый элемент массива. То есть как и в случае с указателями нам доступен адрес, по которому мы можем менять значения. Поэтому следующие объявления функции будут по сути равноценны:
1
2
void print(int numbers[]);
void print(int *numbers);
Передадим в функцию массив:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
void print(int[]);
int main()
{
int nums[] = {1, 2, 3, 4, 5};
print(nums);
return 0;
}
void print(int numbers[])
{
std::cout << "First number: " << numbers[0] << std::endl;
}
В данном случае функция print выводит на консоль первый элемент массива.
Теперь определим параметр как указатель:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
void print(int*);
int main()
{
int nums[] = {1, 2, 3, 4, 5};
print(nums);
return 0;
}
void print(int *numbers)
{
std::cout << "First number: " << *numbers << std::endl;
}
Здесь также в функцию передается массив, однако параметр представляет указатель на первый элемент массива.
Ограничения
Поскольку параметр, определенный как массив, рассматривается именно как указатель на первый элемент, то мы не сможем корректно получить длину массива, например, следующим образом:
1
2
3
4
5
void print(int numbers[])
{
int size = sizeof(numbers) / sizeof(numbers[0]);
std::cout << size << std::endl;
}
И также мы не сможем использовать цикл for для перебора этого массива:
1
2
3
4
5
void print(int numbers[])
{
for (int n : numbers)
std::cout << n << std::endl;
}
Передача маркера конца массива
Чтобы должным образом определять конец массив, перебирать элементы массива, необходимо использовать специальный маркер, который бы сигнализировал об окончании массива. Для этого могут использоваться разные подходы.
Первый подход заключается в том, чтобы один из элементов массива сам сигнализировал о его окончании. В частности, массив символов может представлять строку - набор символов, который завершается нулевым символом '\0'. Фактически нулевой символ служит признком окончания символьного массива:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
void print(char[]);
int main()
{
char chars[] = "Hello";
print(chars);
return 0;
}
void print(char chars[])
{
for (int i = 0; chars[i] != '\0'; i++)
{
std::cout << chars[i] << "\t";
}
}
Второй подход заключается в передаче в функцию размера массива:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
void print(int[], int);
int main()
{
int nums[] = {1, 2, 3, 4, 5};
int n = sizeof(nums)/sizeof(nums[0]);
print(nums, n);
return 0;
}
void print(int numbers[], int n)
{
for(int i=0; i < n; i++)
{
std::cout << numbers[i] << "\t";
}
}
Третий подход заключается в передаче указателя на конец массива. Можно вручную вычислять вычислять указатель на конец массива. А можно использовать встроенные библиотечные функции std::begin() и std::end():
1
2
3
int nums[] = { 1, 2, 3, 4, 5 };
int *begin = std::begin(nums); // указатель на начало массива
int *end = std::end(nums); // указатель на конец массива
Причем end возвращает указатель не на последний элемент, а адрес за последним элементом в массиве.
Применим данные функции:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
void print(int*, int*);
int main()
{
int nums[] = { 1, 2, 3, 4, 5 };
int *begin = std::begin(nums);
int *end = std::end(nums);
print(begin, end);
return 0;
}
void print(int *begin, int *end)
{
for (int *ptr = begin; ptr != end; ptr++)
{
std::cout << *ptr << "\t";
}
}
Константные массивы
Поскольку при передаче массива передается фактически указатель на первый элемент, то используя этот указатель, мы можем изменить элемены массива. Если нет необходимости в изменении массива, то лучше параметр-массив определять как константный:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
void print(const int*, const int*);
void twice(int*, int*);
int main()
{
int nums1[] = { 1, 2, 3, 4, 5 };
int *begin = std::begin(nums1);
int *end = std::end(nums1);
print(begin, end);
std::cout << std::endl;
int nums2[] = { 1, 2, 3, 4, 5 };
begin = std::begin(nums2);
end = std::end(nums2);
twice(begin, end);
for (int *ptr = begin; ptr != end; ptr++)
{
std::cout << *ptr << "\t";
}
std::cout << std::endl;
return 0;
}
void print(const int *begin, const int *end)
{
for (const int *ptr = begin; ptr != end; ptr++)
{
std::cout << *ptr << "\t";
}
}
void twice(int *begin, int *end)
{
for (int *ptr = begin; ptr != end; ptr++)
{
*ptr = *ptr * 2;
}
}
В данном случае функция print просто выводит значения из массива, поэтому параметры этой функции помечаются как константные.
Функция twice изменяет элементы массива - увеличивает их в два раза, поэтому в этой функции параметры являются неконстантными. Причем поле выполнения функции twice массив nums3 будет изменен.
Консольный вывод программы:
1 2 3 4 5
2 4 6 8 10
Передача многомерного массива
Многомерный массив также передается как указатель наего первый элемент. В то же время поскольку элементами многомерного массива являются другие массивы, то указатель на первый элемент многомерного массива фактически будет представлять указатель на массив.
При определении параметра как указателя на массив размер второй размерности (а также всех последующих размерностей) должен быть определен, так как данный размер является частью типа элемента. Пример объявления:
1
void print(int (*numbers)[3])
Здесь предполагается, что передаваемый массив будет двухмерным, и все его подмассивы будут иметь по 3 элемента. Стоит обратить внимание на скобки вокруг имени параметра, которые и позволяют определить параметр как указатель на массив. И от этой ситуации стоит отличать следующую:
1
void print(int *numbers[3])
В данном случае параметр определен как массив указателей, а не как указатель на массив.
Рассмотрим применение указателя на массив в качестве параметра:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
void print(int(*)[3], int);
int main()
{
int table[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
// количество строк или подмассивов
int rowsCount = sizeof(table) / sizeof(table[0]);
print(table, rowsCount);
return 0;
}
void print(int (*numbers)[3], int rowsCount)
{
// количество столбцов или элементов в каждом подмассиве
int columnsCount = sizeof(*numbers)/ sizeof(*numbers[0]);
for(int i =0; i < rowsCount; i++)
{
for (int j = 0; j < columnsCount; j++)
{
std::cout << numbers[i][j] << "\t";
}
std::cout << std::endl;
}
}
В функции main определяется двухмерных массив - он состоит из трех подмассивов. Каждый подмассив имеет по три элемента.
В функцию print вместе с массивом передается и число строк - по сути число подмассивов. В самой функции print получаем количество элементов в каждом подмассиве и с помощью двух циклов перебираем все элементы. С помощью выражения number[0] можно обратиться к первому подмассиву в двухмерном массиве, а с помощью выражения numbers[0][0] - к первому элементу первого подмассива. И таким образом, манипулируя индексами можно перебрать весь двухмерный массив.
В итоге мы получим следующий консольный вывод:
1 2 3
4 5 6
7 8 9
Также мы могли бы определить параметр функци print непосредственно как двухмерный массив, но в этом случае опять же надо было бы указать явным образом вторую размерность:
1
2
3
4
5
6
7
8
9
10
11
12
13
void print(int numbers[][3], int rowsCount)
{
// количество столбцов или элементов в каждом подмассиве
int columnsCount = sizeof(numbers[0])/ sizeof(numbers[0][0]);
for(int i =0; i < rowsCount; i++)
{
for (int j = 0; j < columnsCount; j++)
{
std::cout << numbers[i][j] << "\t";
}
std::cout << std::endl;
}
}