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


324

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

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

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

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

double anArray[10][10];

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

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


1
не может преобразовать параметр 3 из 'double [10] [10]' в "double **"
RogerDarwin

3
В Обслуживаемом ответе показывает только 2 методы [его (2) и (3) являются такими же] , но там уже 4 уникальными способами прохождения 2D массива в функцию .
legends2k

Строго говоря, да, они не являются двумерными массивами, но это соглашение (пусть и ведущее к UB) о наличии массива указателей, каждый из которых указывает на (1D) массив, кажется преобладающим :( Наличие сплющенного 1D массива mxn длина, с вспомогательными функциями / классом для эмуляции 2D-массива, возможно, лучше.
legends2k

ПРОСТОЙ - func(int* mat, int r, int c){ for(int i=0; i<r; i++) for(int j=0; j<c; j++) printf("%d ", *(mat+i*c+j)); }. Назовите это какint mat[3][5]; func(mat[0], 3, 5);
Минхас Камаль

Ответы:


413

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

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

4
@Overflowh Вы можете получить элементы arrayс array[i][j]:)
shengy

14
Для 1-го случая параметр может быть объявлен как int (*a)[10].
Захари

9
Для второго случая параметр может быть объявлен как int **.
Захария

1
@ Зак: Вы правы, на самом деле есть только два случая; один указатель указатель на другой являющийся и один указатель на целочисленный массив размера п т int (*a) [10].
legends2k

3
Варианты 2 и 3 не являются двумерными массивами, поэтому этот ответ вводит в заблуждение. Смотрите это .
Лундин

178

Исправленный размер

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

Как насчет передачи динамически размещаемых массивов в функции в C ++? В стандарте C11 это можно сделать для статически и динамически размещаемых массивов, таких как fn (int col, int row, int array [col] [row]): stackoverflow.com/questions/16004668/… Я сделал вопрос для этой проблемы : stackoverflow.com/questions/27457076/…
42n4

@ 42n4 Case 4 охватывает (в том числе и C ++) это. Для динамически выделенных массивов, просто строка внутри цикла будет меняться от b[i] = a[i];, скажем, b[i] = new int[10];. Можно также сделать bдинамически распределенный, int **b = int *[5];и он все еще будет работать как есть.
legends2k

1
Как адресация array[i][j]работает в функции в 4) ? Поскольку он получил ptr в ptr и не знает значения последнего измерения, что необходимо для перехода к правильной адресации?
user1234567 16.12.14

2
array[i][j]это просто арифметика указателя, т.е. к значению указателя array, он добавляет iи разыменовывает результат как int*, к которому он добавляет jи разыменовывает это местоположение, читая int. Так что нет, для этого не нужно знать какого-либо измерения. Но в этом все дело! Компилятор принимает слово программиста с верой, и если программист ошибался, возникает неопределенное поведение. По этой причине я упомянул, что вариант 4 является наименее безопасным вариантом.
legends2k

В таких случаях структура может служить вам хорошо.
Xofo

40

В качестве модификации первого предложения 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;
}

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


2
Вы должны использовать %pдля печати указатель, и даже тогда вы должны привести его к типу void *, иначе printf()вызовет неопределенное поведение. Кроме того, вы не должны использовать &оператор addressof ( ) при вызове функций, так как функции ожидают аргумент типа double (*)[size_y], тогда как вы в настоящее время передаете их double (*)[10][10]и double (*)[5][5].

Если вы используете шаблоны, создание обоих измерений в качестве аргументов шаблона более уместно и лучше, поскольку низкоуровневый доступ к указателю может быть полностью исключен.
legends2k

3
Это работает, только если размер массива известен во время компиляции.
jeb_is_a_mess

@ Георг Код выше в ответе именно то, что я предложил. Работает в GCC 6.3 - онлайн демо . Вы забыли сделать параметр ссылкой?
legends2k

21

Удивило, что никто еще не упомянул об этом, но вы можете просто создать шаблон для чего-нибудь 2D, поддерживающего семантику [] [].

template <typename TwoD>
void myFunction(TwoD& myArray){
     myArray[x][y] = 5;
     etc...
}

// call with
double anArray[10][10];
myFunction(anArray);

Он работает с любой двумерной «массивоподобной» структурой данных, например std::vector<std::vector<T>>, или пользовательским типом, чтобы максимизировать повторное использование кода.


1
Это должен быть правильный ответ. Это решает все проблемы, упомянутые и некоторые, которые не были упомянуты здесь. Безопасность типов, несовместимость массивов во время компиляции, отсутствие арифметики с указателями, приведение типов, копирование данных. Работает на C и C ++.
OpalApps

Ну, это работает для C ++; C не поддерживает шаблоны. Выполнение этого в C потребует макросов.
Гуннар

20

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

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

2
size_tэто лучший тип для индексов массива, чем int.
Эндрю Томазос

13

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

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

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

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


1
Я предпочитаю это решение, так как библиотека std очень эффективна - кстати, мне нравится dasblinkenlight; Я имел обыкновение использовать dasblikenlicht
mozillanerd

Почти так же эффективно? Да правильно. Погоня за указателем всегда дороже, чем погоня без указателя.
Томас Эдинг

8

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

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

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


8

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

#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

1
Единственная причина, по которой я могу придумать, почему в этом случае нужно манипулировать массивом, заключается в том, что не хватает знаний о том, как работают указатели массива.
Лундин

3
Переменная i должна быть умножена на столбцы, а не на строки, если столбцы и строки не равны, как в этом случае
Андрей Чернуха

4

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

#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

2

Мы можем использовать несколько способов передачи 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;
   }

1

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

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

0

Вы можете использовать шаблон в 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).


0

Если вы хотите перейти 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);

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

0

Вам разрешено опускать крайнее левое измерение, поэтому у вас есть два варианта:

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-мерной информацией.

Подробности здесь

Хотя массивы и указатели не одно и то же : массив может распасться на указатель, но указатель не несет состояния о размере / конфигурации данных, на которые он указывает.

A char **- указатель на блок памяти, содержащий символьные указатели , которые сами указывают на блоки памяти символов. A char [][]- это отдельный блок памяти, который содержит символы. Это влияет на то, как компилятор переводит код и как будет выглядеть конечная производительность.

Источник

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.