Передача 2D-массива в функцию C++

у меня есть функция, которую я хочу взять, в качестве параметра, в 2D массив переменного размера.

пока у меня есть это:

void myFunction(double** myArray){
     myArray[x][y] = 5;
     etc...
}

и я объявил массив в другом месте в моем коде:

double anArray[10][10];
, называя myFunction(anArray) дает мне ошибку.

Я не хочу копировать массив, когда я передаю его. Любые изменения, внесенные в myFunction должно изменить состояние anArray. Если я правильно понял, я хочу только передать в качестве аргумента указатель на В 2D массив. Функция также должна принимать массивы различных размеров. Так, например, [10][10] и [5][5]. Как я могу это сделать?

12 ответов


существует три способа передать 2D-массив функции:

  1. параметр-это 2D массива

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
    
  2. параметр представляет собой массив, содержащий указатели

    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);
    
  3. параметр представляет собой указатель на указатель

    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) массивами; он работает для автоматического (обычно stack-living) массивы только т. е. размерность должна быть известна во время компиляции.

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) передает указатель на массив, а не сам массив. Всегда передача массива as-is становится операцией копирования указателя, которая облегчается природа массива распадается на указатель.

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 целых чисел, в то время как этот синтаксис выглядит как это 2D-массив, но это же указатель на массив из 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;
}

операторы печати показывают, что массивы передаются по ссылке (путем отображения адресов переменных)


вы можете создать шаблон функции, как это:

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...
}

он рассматривает массив как одномерный и использует арифметику для вычисления смещений индексов. В этом case, вы бы определили шаблон следующим образом:

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> >: это почти так же эффективно и значительно удобнее.


удивлен, что никто еще не упомянул об этом, но вы можете просто шаблонно использовать что-либо 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>> или определенный пользователем тип для максимального повторного использования кода.


одномерный массив распадается на указатель, указывающий на первый элемент массива. В то время как 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

одна важная вещь для передачи многомерных массивов:

  • 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.Использование одного указателя: В этом методе мы должны typecast 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;
}`

вот вектор матрицы векторов пример

#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

для этого можно использовать средство шаблона в C++. Я сделал что-то вроде этого:--3-->

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).


мы можем использовать несколько способов передачи 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;
    }
    
  • использование двойного указателя таким образом, мы также typecast 2D array

    #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;
   }