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


324

У мене є функція, яку я хочу взяти за параметр 2D масив змінної величини.

Поки що я маю це:

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 **"
Роджердарвін

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

Відповіді:


413

Існує три способи передачі 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);
    

4
@Overflowh Ви можете отримати елементи arrayз array[i][j]:)
shengy

14
Для першого випадку параметр можна оголосити як int (*a)[10].
Захарій

9
У другому випадку параметр можна оголосити як int **.
Захарій

1
@Zack: Ти маєш рацію, є лише справді два випадки; один - вказівник на вказівник, а інший - єдиний вказівник на цілий масив розміром n, тобто 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 рядок, int масив [col] [рядок]): 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 і не знає значення останнього виміру, яке необхідно виконати зсув для правильної адреси?
користувач1234567

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

Код @Georg вище у відповідь - це саме те, що я запропонував. Він працює в 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

Близько настільки ж ефективний? Так правильно. Переслідування за вказівниками завжди дорожче, ніж переслідування без покажчиків.
Thomas Eding

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

У випадку, якщо ви хочете передати функцію 2-d масиву динамічного розміру до функції, використовуючи деякі вказівники, вам може працювати.

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.