Передача 2D-массива в функцию C++
У меня есть функция, которую я хочу взять в качестве параметра двумерный массив переменного размера.
Пока у меня есть это:
void myFunction(double** myArray){
myArray[x][y] = 5;
etc...
}
И я объявил массив в другом месте в моем коде:
double anArray[10][10];
Тем не менее, призывая myFunction(anArray)
дает мне ошибку.
Я не хочу копировать массив, когда я передаю его. Любые изменения, сделанные в myFunction
должен изменить состояние anArray
, Если я правильно понимаю, я хочу передать в качестве аргумента указатель на двумерный массив. Функция также должна принимать массивы разных размеров. Так, например, [10][10]
а также [5][5]
, Как я могу это сделать?
20 ответов
Есть три способа передать 2D-массив в функцию:
Параметр представляет собой двумерный массив
int array[10][10]; void passFunc(int a[][10]) { // ... } passFunc(array);
Параметр представляет собой массив, содержащий указатели
int *array[10]; for(int i = 0; i < 10; i++) array[i] = new int[10]; void passFunc(int *a[10]) //Array containing pointers { // ... } passFunc(array);
Параметр является указателем на указатель
int **array; array = new int *[10]; for(int i = 0; i <10; i++) array[i] = new int[10]; void passFunc(int **a) { // ... } passFunc(array);
Исправленный размер
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**
}
В качестве модификации первого предложения shengy вы можете использовать шаблоны, чтобы заставить функцию принимать переменную многомерного массива (вместо хранения массива указателей, которые должны управляться и удаляться):
template <size_t size_x, size_t size_y>
void func(double (&arr)[size_x][size_y])
{
printf("%p\n", &arr);
}
int main()
{
double a1[10][10];
double a2[5][5];
printf("%p\n%p\n\n", &a1, &a2);
func(a1);
func(a2);
return 0;
}
Операторы печати предназначены для того, чтобы показать, что массивы передаются по ссылке (путем отображения адресов переменных).
Удивило, что никто еще не упомянул об этом, но вы можете просто создать шаблон для чего-нибудь 2D, поддерживающего семантику [][].
template <typename TwoD>
void myFunction(TwoD& myArray){
myArray[x][y] = 5;
etc...
}
// call with
double anArray[10][10];
myFunction(anArray);
Работает с любой 2D-структурой типа массива, такой как std::vector<std::vector<T>>
или пользовательский тип для максимального повторного использования кода.
Вы можете создать шаблон функции следующим образом:
template<int R, int C>
void myFunction(double (&myArray)[R][C])
{
myArray[x][y] = 5;
etc...
}
Тогда у вас есть оба размера измерений через R и C. Для каждого размера массива будет создана отдельная функция, поэтому, если ваша функция велика, и вы вызываете ее с различными размерами массива, это может быть дорогостоящим. Вы можете использовать его как обертку над такой функцией:
void myFunction(double * arr, int R, int C)
{
arr[x * C + y] = 5;
etc...
}
Он рассматривает массив как одномерный и использует арифметику для определения смещений индексов. В этом случае вы должны определить шаблон следующим образом:
template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
myFunction(*myArray, R, C);
}
anArray[10][10]
это не указатель на указатель, это непрерывный кусок памяти, подходящий для хранения 100 значений типа double, который компилятор знает, как обращаться, потому что вы указали измерения. Вам нужно передать его функции в виде массива. Вы можете опустить размер исходного измерения следующим образом:
void f(double p[][10]) {
}
Однако это не позволит вам передавать массивы с последним измерением, отличным от десяти.
Лучшее решение в C++ - использовать std::vector<std::vector<double> >
: это почти так же эффективно, и значительно удобнее.
Вот пример вектора матрицы векторов
#include <iostream>
#include <vector>
using namespace std;
typedef vector< vector<int> > Matrix;
void print(Matrix& m)
{
int M=m.size();
int N=m[0].size();
for(int i=0; i<M; i++) {
for(int j=0; j<N; j++)
cout << m[i][j] << " ";
cout << endl;
}
cout << endl;
}
int main()
{
Matrix m = { {1,2,3,4},
{5,6,7,8},
{9,1,2,3} };
print(m);
//To initialize a 3 x 4 matrix with 0:
Matrix n( 3,vector<int>(4,0));
print(n);
return 0;
}
выход:
1 2 3 4
5 6 7 8
9 1 2 3
0 0 0 0
0 0 0 0
0 0 0 0
Одномерный массив распадается на указатель указателя, указывающий на первый элемент в массиве. В то время как 2D-массив распадается на указатель, указывающий на первую строку. Итак, прототип функции должен быть -
void myFunction(double (*myArray) [10]);
я бы предпочел std::vector
по сырым массивам.
Вы можете сделать что-то вроде этого...
#include<iostream>
using namespace std;
//for changing values in 2D array
void myFunc(double *a,int rows,int cols){
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
*(a+ i*rows + j)+=10.0;
}
}
}
//for printing 2D array,similar to myFunc
void printArray(double *a,int rows,int cols){
cout<<"Printing your array...\n";
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
cout<<*(a+ i*rows + j)<<" ";
}
cout<<"\n";
}
}
int main(){
//declare and initialize your array
double a[2][2]={{1.5 , 2.5},{3.5 , 4.5}};
//the 1st argument is the address of the first row i.e
//the first 1D array
//the 2nd argument is the no of rows of your array
//the 3rd argument is the no of columns of your array
myFunc(a[0],2,2);
//same way as myFunc
printArray(a[0],2,2);
return 0;
}
Ваш вывод будет следующим...
11.5 12.5
13.5 14.5
Мы можем использовать несколько способов передачи 2D-массива в функцию:
Используя один указатель, мы должны типизировать 2D-массив.
#include<bits/stdc++.h> using namespace std; void func(int *arr, int m, int n) { for (int i=0; i<m; i++) { for (int j=0; j<n; j++) { cout<<*((arr+i*n) + j)<<" "; } cout<<endl; } } int main() { int m = 3, n = 3; int arr[m][n] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; func((int *)arr, m, n); return 0; }
Использование двойного указателя Таким образом, мы также типизируем массив 2d
#include<bits/stdc++.h>
using namespace std;
void func(int **arr, int row, int col)
{
for (int i=0; i<row; i++)
{
for(int j=0 ; j<col; j++)
{
cout<<arr[i][j]<<" ";
}
printf("\n");
}
}
int main()
{
int row, colum;
cin>>row>>colum;
int** arr = new int*[row];
for(int i=0; i<row; i++)
{
arr[i] = new int[colum];
}
for(int i=0; i<row; i++)
{
for(int j=0; j<colum; j++)
{
cin>>arr[i][j];
}
}
func(arr, row, colum);
return 0;
}
Одна важная вещь для передачи многомерных массивов:
First array dimension
не нужно указывать.Second(any any further)dimension
должен быть указан.
1. Когда только второе измерение доступно глобально (или как макрос, или как глобальная константа)
`const int N = 3;
`void print(int arr[][N], int m)
{
int i, j;
for (i = 0; i < m; i++)
for (j = 0; j < N; j++)
printf("%d ", arr[i][j]);
}`
int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
print(arr, 3);
return 0;
}`
2. Использование одного указателя: в этом методе мы должны типизировать 2D-массив при переходе к функции.
`void print(int *arr, int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
printf("%d ", *((arr+i*n) + j));
}
`int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int m = 3, n = 3;
// We can also use "print(&arr[0][0], m, n);"
print((int *)arr, m, n);
return 0;
}`
В случае, если вы хотите передать функции двумерный массив динамического размера, использование некоторых указателей может сработать для вас.
void func1(int *arr, int n, int m){
...
int i_j_the_element = arr[i * m + j]; // use the idiom of i * m + j for arr[i][j]
...
}
void func2(){
...
int arr[n][m];
...
func1(&(arr[0][0]), n, m);
}
#include <iostream>
using std::cout;
template <typename table>
void PrintArray(table& a) {
const size_t rows = sizeof(a) / sizeof(a[0]);
const size_t cols = sizeof(a[0]) / sizeof(a[0][0]);
cout << "Array has " << rows << " rows and " << cols << " columns.\n\n";
for (size_t i = 0; i < rows; i++) {
for (size_t j = 0; j < cols; j++) {
cout << a[i][j] << ' ';
}
cout << '\n';
}
}
int main()
{
size_t a[5][8]{}; // you can change the type and size of the 2D array
PrintArray(a);
return 0;
}
Несмотря на внешний вид, структура данных, подразумеваемаяdouble**
принципиально несовместим с фиксированным c-массивом (double[][]
). Проблема в том, что оба являются популярными (хотя и) ошибочными способами работы с массивами в C (или C++). См. https://www.fftw.org/fftw3_doc/Dynamic-Arrays-in-C_002dThe-Wrong-Way.html .
Если вы не можете контролировать какую-либо часть кода, вам нужен уровень перевода (называемыйadapt
здесь), как описано здесь: https://c-faq.com/aryptr/dynmuldimary.html
Вам нужно сгенерировать вспомогательный массив указателей, указывающих на каждую строку c-массива.
#include<algorithm>
#include<cassert>
#include<vector>
void myFunction(double** myArray) {
myArray[2][3] = 5;
}
template<std::size_t N, std::size_t M>
auto adapt(double(&Carr2D)[N][M]) {
std::array<double*, N> ret;
std::transform(
std::begin(Carr2D), std::end(Carr2D),
ret.begin(),
[](auto&& row) { return &row[0];}
);
return ret;
}
int main() {
double anArray[10][10];
myFunction( adapt(anArray).data() );
assert(anArray[2][3] == 5);
}
(см. рабочий код здесь: )
Если это выглядит как рецепт катастрофы, то это потому, что, как я уже сказал, две структуры данных принципиально несовместимы.
Если вы можете контролировать оба конца кода, в наши дни вам лучше использовать современную (или полусовременную) библиотеку массивов, такую как Boost.MultiArray, Boost.uBLAS, Eigen или Multi . Если массивы будут маленькими , у вас есть библиотеки «крошечных» массивов, например, внутри Eigen, или если вы не можете позволить себе какую-либо зависимость, вы можете попробовать просто сstd::array<std::array<double, N>, M>
.
С Multi вы можете просто сделать это:
#include<multi/array.hpp>
#include<cassert>
namespace multi = boost::multi;
template<class Array2D>
void myFunction(Array2D&& myArray) {
myArray[2][3] = 5;
}
int main() {
multi::array<double, 2> anArray({10, 10});
myFunction(anArray);
assert(anArray[2][3] == 5);
}
(рабочий код: https://godbolt.org/z/7M7KPzbWYhttps://godbolt.org/z/7M7KPzbWY )
Было бы очень просто, если бы вместо 2d массива использовать vector<vector> .
Вы можете брать массивы произвольного количества измерений по ссылке и рекурсивно отделять один слой за раз.
Вот примерprint
функция для демонстрационных целей:
#include <cstddef>
#include <iostream>
#include <iterator>
#include <string>
#include <type_traits>
template <class T, std::size_t N>
void print(const T (&arr)[N], unsigned indent = 0) {
if constexpr (std::rank_v<T> == 0) {
// inner layer - print the values:
std::cout << std::string(indent, ' ') << '{';
auto it = std::begin(arr);
std::cout << *it;
for (++it; it != std::end(arr); ++it) {
std::cout << ", " << *it;
}
std::cout << '}';
} else {
// still more layers to peel off:
std::cout << std::string(indent, ' ') << "{\n";
auto it = std::begin(arr);
print(*it, indent + 1);
for (++it; it != std::end(arr); ++it) {
std::cout << ",\n";
print(*it, indent + 1);
}
std::cout << '\n' << std::string(indent, ' ') << '}';
}
}
Вот пример использования с трехмерным массивом:
int main() {
int array[2][3][5]
{
{
{1, 2, 9, -5, 3},
{6, 7, 8, -45, -7},
{11, 12, 13, 14, 25}
},
{
{4, 5, 0, 33, 34},
{8, 9, 99, 54, 44},
{14, 15, 16, 19, 20}
}
};
print(array);
}
... который выдаст этот вывод:
{
{
{1, 2, 9, -5, 3},
{6, 7, 8, -45, -7},
{11, 12, 13, 14, 25}
},
{
{4, 5, 0, 33, 34},
{8, 9, 99, 54, 44},
{14, 15, 16, 19, 20}
}
}
Вы можете использовать шаблон в C++, чтобы сделать это. Я сделал что-то вроде этого:
template<typename T, size_t col>
T process(T a[][col], size_t row) {
...
}
проблема этого подхода в том, что для каждого значения col, которое вы предоставляете, создается новое определение функции с использованием шаблона. так,
int some_mat[3][3], another_mat[4,5];
process(some_mat, 3);
process(another_mat, 4);
создает экземпляр шаблона дважды, чтобы получить 2 определения функции (одно, где col = 3 и одно, где col = 5).
Вам разрешено опустить крайнее левое измерение, и вы получите два варианта:
void f1(double a[][2][3]) { ... }
void f2(double (*a)[2][3]) { ... }
double a[1][2][3];
f1(a); // ok
f2(a); // ok
То же самое и с указателями:
// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double***’
// double ***p1 = a;
// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double (**)[3]’
// double (**p2)[3] = a;
double (*p3)[2][3] = a; // ok
// compilation error: array of pointers != pointer to array
// double *p4[2][3] = a;
double (*p5)[3] = a[0]; // ok
double *p6 = a[0][1]; // ok
Распад N-мерного массива на указатель на N-1-мерный массив разрешен стандартом C++, так как вы можете потерять самое левое измерение и по-прежнему иметь возможность правильно обращаться к элементам массива с информацией о N-1 измерении.
Подробности здесь
Хотя массивы и указатели не одно и то же: массив может распадаться на указатель, но указатель не передает состояние о размере / конфигурации данных, на которые он указывает.
А char **
- указатель на блок памяти, содержащий указатели на символы, которые сами указывают на блоки памяти символов. Аchar [][]
представляет собой единый блок памяти, содержащий символы. Это влияет на то, как компилятор транслирует код и какова будет конечная производительность.
Обновленный ответ для C++201. Теперь у нас есть. Это рекомендуемый способ для функций принимать массивы произвольной длины.
Пока у меня есть это:
void myFunction(double** myArray){
2D-массивы — это просто массивы массивов.
Эта функция может принимать (указатель на первый элемент) массив указателей для удвоения. Он не может принять (указатель на первый элемент) массив массивов.
Вот пример использования с массивом массивов:
#include <span>
void myFunction(std::span<double[10]> table){
for (auto& row : table) {
for (auto& value : row){
value = 42.;
}
}
}
// usage
double anArray[10][10];
myFunction(anArray);
Например, [10][10] и [5][5]
К сожалению, в приведенном выше примере переменным может быть только самое внешнее измерение (как и в ответах с использованиемint a[][10]
). Если размеры могут быть известны во время компиляции, то шаблон является простым решением, и они уже хорошо описаны в других ответах .
Legends2k предлагает process_pointer_2_pointer
который работает с массивом указателей и, таким образом, позволяет определять оба измерения во время выполнения (строки могут даже иметь разные размеры в одном и том же массиве), но требует преобразования 2D-массива. Хотя этот подход не идеален, его можно использовать и здесь:
void myFunction(std::span<std::span<double>> table){
for (auto& row : table) {
for (auto& value : row){
value = 42.;
}
}
}
// usage
double anArray[10][10];
std::span<double> surrogate[10];
for (std::size_t i = 0; i < std::size(anArray); i++)
{
surrogate[i] = anArray[i];
}
myFunction(surrogate);
Ответ alfC удобен для преобразования. Я оставлю это в качестве упражнения читателю по реализацииadapt
который возвращаетstd::array
изstd::span
.
Я также хочу поддержать рекомендацию alfC использовать правильную абстракцию, которую вы можете найти в библиотеке, если необходимы измерения переменных времени выполнения.
1 Если вы застряли на C++11..17, вам не обязательно возвращаться к старым путям.span
реализован до C++20, и библиотеки, реализующие его, существуют.
Если вы хотите пройти int a[2][3]
в void func(int** pp)
вам нужны вспомогательные шаги следующим образом.
int a[2][3];
int* p[2] = {a[0],a[1]};
int** pp = p;
func(pp);
Как первый [2]
может быть неявно определено, это может быть упрощено далее как.
int a[][3];
int* p[] = {a[0],a[1]};
int** pp = p;
func(pp);