Questa è una domanda abbastanza vecchia, ma inserirò i miei 2 centesimi poiché ci sono molte risposte, ma nessuna mostra tutti i metodi possibili in modo chiaro e conciso (non sono sicuro del bit conciso, poiché questo ha ottenuto un bit fuori mano. TL; DR 😉).
Suppongo che l'OP abbia voluto restituire l'array che è stato passato senza copiare come mezzo per passare direttamente questo al chiamante per essere passato a un'altra funzione per rendere il codice più carino.
Tuttavia, usare un array come questo significa lasciarlo decadere in un puntatore e fare in modo che il compilatore lo tratti come un array. Ciò può comportare bug sottili se si passa in un array come, con la funzione che prevede che avrà 5 elementi, ma il chiamante passa effettivamente in qualche altro numero.
Ci sono alcuni modi per gestirlo meglio. Passa a std::vector
o std::array
(non sono sicuro se std::array
esistesse nel 2010 quando è stata posta la domanda). È quindi possibile passare l'oggetto come riferimento senza copiare / spostare l'oggetto.
std::array<int, 5>& fillarr(std::array<int, 5>& arr)
{
// (before c++11)
for(auto it = arr.begin(); it != arr.end(); ++it)
{ /* do stuff */ }
// Note the following are for c++11 and higher. They will work for all
// the other examples below except for the stuff after the Edit.
// (c++11 and up)
for(auto it = std::begin(arr); it != std::end(arr); ++it)
{ /* do stuff */ }
// range for loop (c++11 and up)
for(auto& element : arr)
{ /* do stuff */ }
return arr;
}
std::vector<int>& fillarr(std::vector<int>& arr)
{
for(auto it = arr.begin(); it != arr.end(); ++it)
{ /* do stuff */ }
return arr;
}
Tuttavia, se si insiste nel giocare con le matrici C, utilizzare un modello che consenta di conservare il numero di elementi nell'array.
template <size_t N>
int(&fillarr(int(&arr)[N]))[N]
{
// N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
for(int* it = arr; it != arr + N; ++it)
{ /* do stuff */ }
return arr;
}
Tranne che sembra brutto e super difficile da leggere. Ora uso qualcosa per aiutare con ciò che non esisteva nel 2010, che uso anche per i puntatori a funzione:
template <typename T>
using type_t = T;
template <size_t N>
type_t<int(&)[N]> fillarr(type_t<int(&)[N]> arr)
{
// N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
for(int* it = arr; it != arr + N; ++it)
{ /* do stuff */ }
return arr;
}
Questo sposta il tipo in cui ci si aspetterebbe che sia, rendendolo molto più leggibile. Ovviamente, l'uso di un modello è superfluo se non hai intenzione di usare nient'altro che 5 elementi, quindi puoi ovviamente codificarlo con hard disk:
type_t<int(&)[5]> fillarr(type_t<int(&)[5]> arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
Come ho detto, il mio type_t<>
trucco non avrebbe funzionato al momento in cui è stata posta questa domanda. Il meglio che avresti potuto sperare allora era usare un tipo in una struttura:
template<typename T>
struct type
{
typedef T type;
};
typename type<int(&)[5]>::type fillarr(typename type<int(&)[5]>::type arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
Che inizia a sembrare di nuovo piuttosto brutto, ma almeno è ancora più leggibile, anche se typename
potrebbe essere stato facoltativo allora a seconda del compilatore, risultando in:
type<int(&)[5]>::type fillarr(type<int(&)[5]>::type arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
E poi ovviamente avresti potuto specificare un tipo specifico, piuttosto che usare il mio aiuto.
typedef int(&array5)[5];
array5 fillarr(array5 arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
Allora, le funzioni libere std::begin()
e std::end()
non esistevano, anche se avrebbero potuto essere facilmente implementate. Ciò avrebbe permesso di scorrere sull'array in un modo più sicuro poiché hanno senso su un array C, ma non su un puntatore.
Per quanto riguarda l'accesso all'array, è possibile passarlo a un'altra funzione che accetta lo stesso tipo di parametro o creare un alias (che non avrebbe molto senso in quanto l'originale è già in quell'ambito). Accedere a un riferimento di array è come accedere all'array originale.
void other_function(type_t<int(&)[5]> x) { /* do something else */ }
void fn()
{
int array[5];
other_function(fillarr(array));
}
o
void fn()
{
int array[5];
auto& array2 = fillarr(array); // alias. But why bother.
int forth_entry = array[4];
int forth_entry2 = array2[4]; // same value as forth_entry
}
Per riassumere, è meglio non consentire il decadimento di un array in un puntatore se si intende iterare su di esso. È solo una cattiva idea in quanto impedisce al compilatore di proteggerti dal spararti ai piedi e rende il tuo codice più difficile da leggere. Cerca sempre di aiutare il compilatore a aiutarti mantenendo i tipi il più a lungo possibile a meno che tu non abbia una buona ragione per non farlo.
modificare
Oh, e per completezza, puoi permettere che degrada a un puntatore, ma questo disaccoppia l'array dal numero di elementi che contiene. Questo viene fatto molto in C / C ++ e di solito viene mitigato passando il numero di elementi nell'array. Tuttavia, il compilatore non può aiutarti se commetti un errore e passi il valore sbagliato al numero di elementi.
// separate size value
int* fillarr(int* arr, size_t size)
{
for(int* it = arr; it != arr + size; ++it)
{ /* do stuff */ }
return arr;
}
Invece di passare la dimensione, puoi passare il puntatore di fine, che punterà a uno oltre la fine dell'array. Questo è utile perché rende qualcosa che è più vicino agli algoritmi std, che prendono un puntatore di inizio e fine, ma ciò che ritorni ora è solo qualcosa che devi ricordare.
// separate end pointer
int* fillarr(int* arr, int* end)
{
for(int* it = arr; it != end; ++it)
{ /* do stuff */ }
return arr;
}
In alternativa, puoi documentare che questa funzione richiederà solo 5 elementi e spera che l'utente della tua funzione non faccia nulla di stupido.
// I document that this function will ONLY take 5 elements and
// return the same array of 5 elements. If you pass in anything
// else, may nazal demons exit thine nose!
int* fillarr(int* arr)
{
for(int* it = arr; it != arr + 5; ++it)
{ /* do stuff */ }
return arr;
}
Si noti che il valore restituito ha perso il tipo originale ed è degradato a un puntatore. Per questo motivo, ora sei da solo per assicurarti di non sovraccaricare l'array.
Potresti passare a std::pair<int*, int*>
, che puoi usare per inizio e fine e passarlo, ma poi smette davvero di sembrare un array.
std::pair<int*, int*> fillarr(std::pair<int*, int*> arr)
{
for(int* it = arr.first; it != arr.second; ++it)
{ /* do stuff */ }
return arr; // if you change arr, then return the original arr value.
}
void fn()
{
int array[5];
auto array2 = fillarr(std::make_pair(&array[0], &array[5]));
// Can be done, but you have the original array in scope, so why bother.
int fourth_element = array2.first[4];
}
o
void other_function(std::pair<int*, int*> array)
{
// Can be done, but you have the original array in scope, so why bother.
int fourth_element = array2.first[4];
}
void fn()
{
int array[5];
other_function(fillarr(std::make_pair(&array[0], &array[5])));
}
Abbastanza divertente, questo è molto simile a come std::initializer_list
funziona (c ++ 11), ma non funzionano in questo contesto.