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