Исправленный размер
1. Передать по ссылке
template <size_t rows, size_t cols>
void process_2d_array_template(int (&array)[rows][cols])
{
std::cout << __func__ << std::endl;
for (size_t i = 0; i < rows; ++i)
{
std::cout << i << ": ";
for (size_t j = 0; j < cols; ++j)
std::cout << array[i][j] << '\t';
std::cout << std::endl;
}
}
В C ++ передача массива по ссылке без потери информации об измерениях, вероятно, является наиболее безопасной, поскольку не нужно беспокоиться о том, что вызывающая сторона передаст неправильное измерение (флаги компилятора при несовпадении). Однако это невозможно с динамическими (freestore) массивами; он работает только для автоматических ( обычно стековых ) массивов, т.е. размерность должна быть известна во время компиляции.
2. Пройдите по указателю
void process_2d_array_pointer(int (*array)[5][10])
{
std::cout << __func__ << std::endl;
for (size_t i = 0; i < 5; ++i)
{
std::cout << i << ": ";
for (size_t j = 0; j < 10; ++j)
std::cout << (*array)[i][j] << '\t';
std::cout << std::endl;
}
}
Эквивалент предыдущего метода на C передает массив по указателю. Это не следует путать с передачей типа потерянного указателя массива (3) , который является распространенным, популярным методом, хотя и менее безопасным, чем этот, но более гибким. Подобно (1) , используйте этот метод, когда все размеры массива фиксированы и известны во время компиляции. Обратите внимание, что при вызове функции должен передаваться адрес массива, process_2d_array_pointer(&a)
а не адрес первого элемента при распаде process_2d_array_pointer(a)
.
Размер переменной
Они унаследованы от C, но менее безопасны, у компилятора нет способа проверить, гарантируя, что вызывающая сторона передает требуемые измерения. Функция полагается только на то, что передает вызывающая сторона в качестве измерения. Они более гибкие, чем описанные выше, поскольку массивы различной длины могут быть переданы им неизменно.
Следует помнить, что нет такой вещи, как передача массива непосредственно в функцию в C [в то время как в C ++ они могут передаваться как ссылка (1) ]; (2) передает указатель на массив, а не сам массив. Всегда передача массива как есть становится операцией копирования указателя, чему способствует природа распада массива в указатель .
3. Передать (значение) указатель на распавшийся тип
// int array[][10] is just fancy notation for the same thing
void process_2d_array(int (*array)[10], size_t rows)
{
std::cout << __func__ << std::endl;
for (size_t i = 0; i < rows; ++i)
{
std::cout << i << ": ";
for (size_t j = 0; j < 10; ++j)
std::cout << array[i][j] << '\t';
std::cout << std::endl;
}
}
Хотя int array[][10]
это разрешено, я бы не рекомендовал использовать этот синтаксис выше, поскольку приведенный выше синтаксис проясняет, что идентификатор array
представляет собой один указатель на массив из 10 целых чисел, хотя этот синтаксис выглядит как двумерный массив, но тот же указатель на массив из 10 целых чисел. Здесь мы знаем количество элементов в одной строке (то есть размер столбца, здесь 10), но количество строк неизвестно и, следовательно, должно быть передано в качестве аргумента. В этом случае есть некоторая безопасность, так как компилятор может пометить, когда передан указатель на массив со вторым измерением, не равным 10. Первое измерение является переменной частью и может быть опущено. См. Здесь обоснование того, почему только первое измерение может быть опущено.
4. Передать указатель на указатель
// int *array[10] is just fancy notation for the same thing
void process_pointer_2_pointer(int **array, size_t rows, size_t cols)
{
std::cout << __func__ << std::endl;
for (size_t i = 0; i < rows; ++i)
{
std::cout << i << ": ";
for (size_t j = 0; j < cols; ++j)
std::cout << array[i][j] << '\t';
std::cout << std::endl;
}
}
Опять же есть альтернативный синтаксис, int *array[10]
который такой же, как int **array
. В этом синтаксисе [10]
игнорируется, поскольку он превращается в указатель, становясь тем самым int **array
. Возможно, это просто подсказка вызывающей стороне, что переданный массив должен иметь не менее 10 столбцов, даже если требуется количество строк. В любом случае компилятор не помечает для каких-либо нарушений длины / размера (он только проверяет, является ли переданный тип указателем на указатель), следовательно, здесь имеет значение количество строк и столбцов в качестве параметра.
Примечание: (4) является наименее безопасным вариантом, так как он вряд ли имеет какую-либо проверку типов и является наиболее неудобным. Нельзя законно передать 2D-массив этой функции; C-FAQ осуждает обычный обходной путь, int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);
поскольку он может потенциально привести к неопределенному поведению из-за выравнивания массива. Правильный способ передачи массива в этом методе приводит нас к неудобной части, то есть нам нужен дополнительный (суррогатный) массив указателей, каждый элемент которого указывает на соответствующую строку фактического, подлежащего передаче массива; этот суррогат затем передается функции (см. ниже); все это для выполнения той же работы, что и вышеописанные методы, которые более безопасны, чище и, возможно, быстрее.
Вот программа-драйвер для проверки вышеуказанных функций:
#include <iostream>
// copy above functions here
int main()
{
int a[5][10] = { { } };
process_2d_array_template(a);
process_2d_array_pointer(&a); // <-- notice the unusual usage of addressof (&) operator on an array
process_2d_array(a, 5);
// works since a's first dimension decays into a pointer thereby becoming int (*)[10]
int *b[5]; // surrogate
for (size_t i = 0; i < 5; ++i)
{
b[i] = a[i];
}
// another popular way to define b: here the 2D arrays dims may be non-const, runtime var
// int **b = new int*[5];
// for (size_t i = 0; i < 5; ++i) b[i] = new int[10];
process_pointer_2_pointer(b, 5, 10);
// process_2d_array(b, 5);
// doesn't work since b's first dimension decays into a pointer thereby becoming int**
}