Передача 2D-массива в функцию C ++ - PullRequest
276 голосов
/ 07 января 2012

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

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

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

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

double anArray[10][10];

Однако, вызов myFunction(anArray) дает мне ошибку.

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

Ответы [ 14 ]

378 голосов
/ 07 января 2012

Существует три способа передачи двумерного массива в функцию:

  1. Параметр представляет собой двумерный массив

    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);
    
150 голосов
/ 10 июля 2013

фиксированный размер

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**
}
40 голосов
/ 07 января 2012

Модификация первого предложения Шенги: вы можете использовать шаблоны, чтобы заставить функцию принимать переменную многомерного массива (вместо хранения массива указателей, которые нужно управлять и удалять):

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

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

20 голосов
/ 07 января 2012

Вы можете создать шаблон функции следующим образом:

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);
}
16 голосов
/ 22 декабря 2014

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

10 голосов
/ 07 января 2012

anArray[10][10] - не указатель на указатель, это непрерывный кусок памяти, подходящий для хранения 100 значений типа double, который компилятор знает, как обращаться, потому что вы указали измерения.Вам нужно передать его функции в виде массива.Вы можете опустить размер исходного измерения следующим образом:

void f(double p[][10]) {
}

Однако это не позволит вам передавать массивы с последним измерением, отличным от десяти.

Лучшее решение в C ++использовать std::vector<std::vector<double> >: это почти так же эффективно и значительно удобнее.

8 голосов
/ 20 сентября 2014

Вы можете сделать что-то вроде этого ...

#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
8 голосов
/ 07 января 2012

Одномерный массив распадается на указатель указателя, указывающий на первый элемент в массиве. В то время как 2D-массив распадается на указатель, указывающий на первую строку. Итак, прототип функции должен быть -

void myFunction(double (*myArray) [10]);

Я бы предпочел std::vector необработанным массивам.

4 голосов
/ 07 июля 2018

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

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

output:

1 2 3 4
5 6 7 8
9 1 2 3

0 0 0 0
0 0 0 0
0 0 0 0
1 голос
/ 20 июня 2018

Мы можем использовать несколько способов передачи двумерного массива в функцию:

  • Используя один указатель , мы должны типизировать двумерный массив.

    #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;
   }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...