фиксированный размер
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;
}
}
С-эквивалент предыдущего метода - передача массива по указателю. Это не следует путать с передачей указателем массива типа 1011 * (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**
}