Фіксований розмір
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**
}