Passando un array 2D a una funzione C ++


324

Ho una funzione che voglio prendere, come parametro, un array 2D di dimensioni variabili.

Finora ho questo:

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

E ho dichiarato un array altrove nel mio codice:

double anArray[10][10];

Tuttavia, chiamare myFunction(anArray)mi dà un errore.

Non voglio copiare l'array quando lo passo. Eventuali modifiche apportate myFunctiondovrebbero alterare lo stato di anArray. Se capisco correttamente, voglio solo passare come argomento un puntatore a un array 2D. La funzione deve accettare anche matrici di dimensioni diverse. Quindi, per esempio, [10][10]e [5][5]. Come posso fare questo?


1
impossibile convertire il parametro 3 da 'double [10] [10]' a 'double **'
RogerDarwin

3
La risposta accettata mostra solo 2 tecniche [i suoi (2) e (3) sono gli stessi] ma ci sono 4 modi unici di passare un array 2D a una funzione .
legends2k

A rigor di termini, sì, non sono array 2D, ma questa convenzione (anche se porta a UB) di avere un array di puntatori, ciascuno che punta a un array (1D), sembra essere prevalente :( Avere un array 1D appiattito di mxn lunghezza, con funzioni di supporto / classe per emulare un array 2D è forse migliore
legends2k

PIÙ FACILE - 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)); }. Chiamalo come-int mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

Risposte:


413

Esistono tre modi per passare un array 2D a una funzione:

  1. Il parametro è un array 2D

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. Il parametro è un array che contiene puntatori

    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. Il parametro è un puntatore a un puntatore

    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 Puoi ottenere gli elementi di arraycon array[i][j]:)
shengy

14
Per il primo caso, il parametro può essere dichiarato come int (*a)[10].
Zaccaria,

9
Per il secondo caso, il parametro può essere dichiarato come int **.
Zaccaria

1
@Zack: hai ragione, ci sono solo due casi; uno è un puntatore a puntatore e l'altro è un singolo puntatore a un array intero di dimensioni n ie int (*a) [10].
legends2k

3
I casi 2 e 3 non sono array 2D, quindi questa risposta è fuorviante. Vedere questo .
Lundin,

178

Taglia unica

1. Passa per riferimento

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

Nel C ++ passare l'array per riferimento senza perdere le informazioni sulla dimensione è probabilmente il più sicuro, poiché non è necessario preoccuparsi che il chiamante passi una dimensione errata (flag del compilatore in caso di mancata corrispondenza). Tuttavia, ciò non è possibile con matrici dinamiche (freestore); funziona solo per array automatici ( generalmente stack-living ), ovvero la dimensionalità dovrebbe essere nota al momento della compilazione.

2. Passa per puntatore

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

L'equivalente C del metodo precedente sta passando l'array tramite puntatore. Questo non deve essere confuso con il passaggio dal tipo di puntatore decaduto dell'array (3) , che è il metodo comune e popolare, sebbene meno sicuro di questo ma più flessibile. Come (1) , utilizzare questo metodo quando tutte le dimensioni dell'array sono fisse e note al momento della compilazione. Si noti che quando si chiama la funzione è necessario passare process_2d_array_pointer(&a)l'indirizzo dell'array e non l'indirizzo del primo elemento per decadimento process_2d_array_pointer(a).

Dimensione variabile

Questi sono ereditati da C ma sono meno sicuri, il compilatore non ha modo di verificare, garantendo che il chiamante stia passando le dimensioni richieste. La funzione si basa solo su ciò che il chiamante passa come dimensione (e). Questi sono più flessibili di quelli precedenti poiché matrici di lunghezze diverse possono essere invariate su di essi.

Va ricordato che non esiste un passaggio di un array direttamente a una funzione in C [mentre in C ++ possono essere passati come riferimento (1) ]; (2) sta passando un puntatore alla matrice e non alla matrice stessa. Passare sempre un array così com'è diventa un'operazione di copia puntatore che è facilitata dalla natura dell'array di decadimento in un puntatore .

3. Passare (valore) un puntatore al tipo decaduto

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

Sebbene int array[][10]sia consentito, non lo consiglierei sopra la sintassi sopra poiché la sintassi sopra chiarisce che l'identificatore arrayè un singolo puntatore a un array di 10 numeri interi, mentre questa sintassi sembra un array 2D ma è lo stesso puntatore a una matrice di 10 numeri interi. Qui conosciamo il numero di elementi in una singola riga (ovvero la dimensione della colonna, qui 10) ma il numero di righe è sconosciuto e quindi deve essere passato come argomento. In questo caso c'è una certa sicurezza poiché il compilatore può contrassegnare quando viene passato un puntatore a un array con seconda dimensione non uguale a 10. La prima dimensione è la parte variabile e può essere omessa. Vedi qui per la logica del perché è consentito omettere solo la prima dimensione.

4. Passare dal puntatore a un puntatore

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

Ancora una volta c'è una sintassi alternativa int *array[10]che è la stessa di int **array. In questa sintassi [10]viene ignorato mentre decade in un puntatore diventando così int **array. Forse è solo uno spunto per il chiamante che l'array passato deve avere almeno 10 colonne, anche se è richiesto il conteggio delle righe. In ogni caso il compilatore non segnala eventuali violazioni di lunghezza / dimensione (controlla solo se il tipo passato è un puntatore a puntatore), quindi richiede sia il conteggio delle righe che delle colonne come parametro ha senso qui.

Nota: (4) è l'opzione meno sicura poiché non ha quasi alcun controllo del tipo e la più scomoda. Non si può legittimamente passare un array 2D a questa funzione; C-FAQ condanna la solita soluzione alternativa int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);in quanto potrebbe comportare un comportamento indefinito a causa dell'appiattimento dell'array. Il modo giusto di passare un array in questo metodo ci porta alla parte scomoda, cioè abbiamo bisogno di un array aggiuntivo (surrogato) di puntatori con ciascuno dei suoi elementi che punta alla rispettiva riga dell'array reale, da passare; questo surrogato viene quindi passato alla funzione (vedi sotto); tutto questo per ottenere lo stesso lavoro svolto con i metodi di cui sopra che sono più sicuri, più puliti e forse più veloci.

Ecco un programma driver per testare le funzioni sopra:

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

Che dire del passaggio di array allocati dinamicamente alle funzioni in C ++? Nello standard C11 può essere fatto per array allocati staticamente e dinamicamente come quello fn (int col, int row, int array [col] [row]): stackoverflow.com/questions/16004668/… Ho fatto la domanda per questo problema : stackoverflow.com/questions/27457076/...
42n4

@ 42n4 Case 4 copre (anche per C ++). Per le matrici allocate dinamicamente, solo la linea all'interno del loop cambierebbe da b[i] = a[i];, diciamo, a b[i] = new int[10];. Uno può anche essere ballocato dinamicamente int **b = int *[5];e funzionerà ancora così com'è.
legends2k,

1
Come funziona l'indirizzamento array[i][j]nella funzione in 4) ? Perché ha ricevuto ptr in ptr e non conosce il valore dell'ultima dimensione, che è necessario per eseguire uno spostamento per l'indirizzamento corretto?
user1234567

2
array[i][j]è solo aritmetica del puntatore, cioè al valore del puntatore array, aggiungerebbe ie dereferirebbe il risultato come int*, al quale aggiungerebbe je dereferirebbe quella posizione, leggendo un int. Quindi no, non è necessario conoscere alcuna dimensione per questo. Ma questo è il punto! Il compilatore prende fede alla parola del programmatore e se il programmatore non è corretto, ne consegue un comportamento indefinito. Questo è il motivo per cui avevo menzionato che il caso 4 è l'opzione meno sicura.
legends2k

In tali casi, una struttura può essere utile.
Xofo

40

Una modifica al primo suggerimento di shengy, è possibile utilizzare i modelli per fare in modo che la funzione accetti una variabile di array multidimensionale (invece di archiviare un array di puntatori che devono essere gestiti ed eliminati):

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

Le istruzioni di stampa sono lì per mostrare che le matrici vengono passate per riferimento (visualizzando gli indirizzi delle variabili)


2
Dovresti usare %pper stampare un puntatore e anche allora, devi lanciarlo void *, altrimenti printf()invoca un comportamento indefinito. Inoltre, non si deve usare l' &operatore addressof ( ) quando si chiamano le funzioni, poiché le funzioni prevedono un argomento di tipo double (*)[size_y], mentre attualmente le si passa double (*)[10][10]e double (*)[5][5].

Se stai utilizzando i modelli per rendere entrambe le dimensioni come argomenti del modello è più appropriato ed è migliore poiché l'accesso al puntatore di basso livello potrebbe essere completamente evitato.
legends2k

3
Funziona solo se la dimensione dell'array è nota al momento della compilazione.
jeb_is_a_mess,

Il codice @Georg sopra in risposta è esattamente quello che avevo suggerito. Funziona in GCC 6.3 - demo online . Hai dimenticato di fare del parametro un riferimento?
legends2k

21

Sorpreso dal fatto che nessuno lo abbia ancora menzionato, ma puoi semplicemente creare un template su qualsiasi 2D che supporti la semantica [] [].

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

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

Funziona con qualsiasi struttura di dati 2D "simile a matrice", come std::vector<std::vector<T>>, o con un tipo definito dall'utente per massimizzare il riutilizzo del codice.


1
Questa dovrebbe essere la risposta giusta. Risolve tutti i problemi citati e alcuni che non sono stati menzionati qui. Sicurezza del tipo, incompatibilità dei tempi di compilazione delle matrici, nessuna aritmetica del puntatore, nessuna trasmissione del tipo, nessuna copia dei dati. Funziona per C e C ++.
OpalApps

Bene, questo funziona per C ++; C non supporta i modelli. Farlo in C richiederebbe macro.
Gunnar,

20

È possibile creare un modello di funzione come questo:

template<int R, int C>
void myFunction(double (&myArray)[R][C])
{
    myArray[x][y] = 5;
    etc...
}

Quindi hai entrambe le dimensioni della dimensione tramite R e C. Verrà creata una funzione diversa per ogni dimensione dell'array, quindi se la tua funzione è grande e la chiami con una varietà di dimensioni dell'array diverse, questo può essere costoso. Puoi usarlo come un wrapper su una funzione come questa però:

void myFunction(double * arr, int R, int C)
{
    arr[x * C + y] = 5;
    etc...
}

Tratta l'array come unidimensionale e usa l'aritmetica per capire gli offset degli indici. In questo caso, definiresti il ​​modello in questo modo:

template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
    myFunction(*myArray, R, C);
}

2
size_tè il tipo migliore per gli indici di array di int.
Andrew Tomazos,

13

anArray[10][10]non è un puntatore a un puntatore, è un pezzo di memoria contiguo adatto per memorizzare 100 valori di tipo double, che il compilatore sa come affrontare perché hai specificato le dimensioni. È necessario passarlo a una funzione come un array. È possibile omettere la dimensione della dimensione iniziale, come indicato di seguito:

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

Tuttavia, ciò non consente di passare array con l'ultima dimensione diversa da dieci.

La migliore soluzione in C ++ è usare std::vector<std::vector<double> >: è quasi altrettanto efficiente e significativamente più conveniente.


1
Preferisco questa soluzione poiché la libreria std è molto efficiente - dal modo in cui mi piace dasblinkenlight; Usavo dasblikenlicht
mozillanerd il

Quasi altrettanto efficiente? Si, come no. L'inseguimento puntatore è sempre più costoso dell'inseguimento senza puntatore.
Thomas Eding,

8

L'array monodimensionale decade in un puntatore puntatore che punta al primo elemento dell'array. Mentre un array 2D decade in un puntatore che punta alla prima riga. Quindi, il prototipo di funzione dovrebbe essere -

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

Preferirei agli std::vectorarray grezzi.


8

Puoi fare qualcosa del genere ...

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

Il tuo output sarà il seguente ...

11.5  12.5
13.5  14.5

1
L'unico motivo per cui riesco a trovare il motivo per cui uno dovrebbe manipolare l'array in questo caso, è perché manca la conoscenza di come funzionano i puntatori dell'array.
Lundin,

3
la variabile i deve essere moltiplicata per le colonne, non per le righe a meno che colonne e righe non siano uguali come in questo caso
Andrey Chernukha,

4

Ecco un vettore di esempio di matrice di vettori

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

produzione:

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

Possiamo usare diversi modi per passare un array 2D a una funzione:

  • Usando un singolo puntatore dobbiamo tipizzare l'array 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;
    }
  • Uso del doppio puntatore In questo modo, abbiamo anche scritto a caldo l'array 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

Una cosa importante per il passaggio di array multidimensionali è:

  • First array dimension non è necessario specificare.
  • Second(any any further)dimension deve essere specificato.

1.Quando è disponibile solo una seconda dimensione a livello globale (come macro o come costante globale)

`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.Utilizzando un singolo puntatore : in questo metodo, quando si passa alla funzione, è necessario digitare in modo casuale l'array 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

È possibile utilizzare la funzione modello in C ++ per fare ciò. Ho fatto qualcosa del genere:

template<typename T, size_t col>
T process(T a[][col], size_t row) {
...
}

il problema con questo approccio è che per ogni valore di col che viene fornito, la nuova definizione di funzione viene istanziata usando il modello. così,

int some_mat[3][3], another_mat[4,5];
process(some_mat, 3);
process(another_mat, 4);

crea un'istanza del modello due volte per produrre 2 definizioni di funzione (una dove col = 3 e una dove col = 5).


0

Se si desidera passare int a[2][3]a void func(int** pp)è necessario passaggi ausiliari come segue.

int a[2][3];
int* p[2] = {a[0],a[1]};
int** pp = p;

func(pp);

Poiché il primo [2]può essere implicitamente specificato, può essere ulteriormente semplificato come.

int a[][3];
int* p[] = {a[0],a[1]};
int** pp = p;

func(pp);

0

Nel caso in cui si desideri passare una matrice 2D di dimensioni dinamiche a una funzione, l'utilizzo di alcuni puntatori potrebbe funzionare per te.

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

Ti è permesso omettere la dimensione più a sinistra e quindi finisci con due opzioni:

void f1(double a[][2][3]) { ... }

void f2(double (*a)[2][3]) { ... }

double a[1][2][3];

f1(a); // ok
f2(a); // ok 

Questo è lo stesso con i puntatori:

// 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

Il decadimento di una matrice dimensionale N in un puntatore alla matrice dimensionale N-1 è consentito dallo standard C ++ , poiché è possibile perdere la dimensione più a sinistra e poter accedere correttamente agli elementi della matrice con le informazioni sulla dimensione N-1.

Dettagli qui

Tuttavia, gli array e i puntatori non sono gli stessi : un array può decadere in un puntatore, ma un puntatore non porta stato sulla dimensione / configurazione dei dati a cui punta.

A char **è un puntatore a un blocco di memoria contenente puntatori di caratteri , che a loro volta indicano blocchi di memoria di caratteri. A char [][]è un singolo blocco di memoria che contiene caratteri. Ciò ha un impatto su come il compilatore traduce il codice e su come sarà la prestazione finale.

fonte

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.