Come funziona il range-based for per gli array semplici?


88

In C ++ 11 puoi usare un range-based for, che funge da foreachdi altri linguaggi. Funziona anche con semplici array C:

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

Come fa a sapere quando fermarsi? Funziona solo con array statici che sono stati dichiarati nello stesso ambito in cui forè utilizzato? Come lo useresti forcon gli array dinamici?


10
Non ci sono array "dinamici" in C o C ++ di per sé - ci sono tipi di array e poi ci sono puntatori che possono o non possono puntare a un array oa un blocco di memoria allocato dinamicamente che si comporta principalmente come un array. Per ogni array di tipo T [n], la sua dimensione è codificata nel tipo ed è accessibile da for. Ma nel momento in cui l'array decade in un puntatore, le informazioni sulla dimensione vengono perse.
JohannesD

1
Nel tuo esempio, il numero di elementi in numbersè sizeof(numbers)/sizeof(int), ad esempio.
JohannesD

Risposte:


57

Funziona per qualsiasi espressione il cui tipo è un array. Per esempio:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

Per una spiegazione più dettagliata, se il tipo di espressione passato a destra di :è un tipo di matrice, il ciclo itera da ptra ptr + size( ptrpuntando al primo elemento della matrice,size essendo il conteggio degli elementi dell'array).

Ciò è in contrasto con i tipi definiti dall'utente, che funzionano cercando begine endcome membri se si passa un oggetto classe o (se non ci sono membri chiamati in questo modo) funzioni non membro. Queste funzioni produrranno gli iteratori di inizio e di fine (che puntano rispettivamente a subito dopo l'ultimo elemento e l'inizio della sequenza).

Questa domanda chiarisce perché esiste questa differenza.


8
Penso che la domanda fosse come funziona, non quando funziona
vedi il

1
@sehe la domanda conteneva più "?" es. Uno era "Funziona con ...?". Ho spiegato sia come che quando funziona.
Johannes Schaub - litb

8
@JohannesSchaub: Penso che il problema del "come" qui sia come ottenere esattamente la dimensione di un oggetto di un tipo di array in primo luogo (a causa della confusione tra puntatori e array, non quasi tutti sanno che la dimensione di un array è a disposizione del programmatore.)
JohannesD

Credo che cerchi solo i non membri begin`end . It just happens that std :: begin `std::endusa le funzioni membro, e sarà usato se non è disponibile una corrispondenza migliore.
Dennis Zickefoose

3
@Dennis no a Madrid si è deciso di cambiare la situazione e di favorire i membri iniziali e finali. Non favorire i membri iniziali e finali ha causato ambiguità difficili da evitare.
Johannes Schaub - litb

45

Penso che la parte più importante di questa domanda sia, come C ++ sa qual è la dimensione di un array (almeno volevo saperlo quando ho trovato questa domanda).

C ++ conosce la dimensione di un array, perché fa parte della definizione dell'array: è il tipo di variabile. Un compilatore deve conoscere il tipo.

Poiché C ++ 11 std::extentpuò essere utilizzato per ottenere la dimensione di un array:

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

Ovviamente questo non ha molto senso, perché devi fornire esplicitamente la dimensione nella prima riga, che poi ottieni nella seconda riga. Ma puoi anche usare decltypee poi diventa più interessante:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;

6
Questo è effettivamente quello che stavo chiedendo inizialmente. :)
Paul Manta

19

Secondo l'ultimo C ++ Working Draft (n3376), l'istruzione per range è equivalente a quanto segue:

{
    auto && __range = range-init;
    for (auto __begin = begin-expr,
              __end = end-expr;
            __begin != __end;
            ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

Quindi sa come fermarsi allo stesso modo di un forciclo regolare che utilizza gli iteratori.

Penso che potresti cercare qualcosa di simile al seguente per fornire un modo per utilizzare la sintassi di cui sopra con array che consistono solo di un puntatore e di una dimensione (array dinamici):

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

Questo modello di classe può quindi essere utilizzato per creare un intervallo, su cui è possibile iterare utilizzando il nuovo intervallo per la sintassi. Lo sto usando per scorrere tutti gli oggetti di animazione in una scena che viene importata utilizzando una libreria che restituisce solo un puntatore a un array e una dimensione come valori separati.

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

Questa sintassi è, a mio parere, molto più chiara di quella che otterresti usando std::for_eacho un semplice forciclo.


3

Sa quando fermarsi perché conosce i limiti degli array statici.

Non sono sicuro di cosa intendi per "array dinamici", in ogni caso, se non iterando su array statici, informalmente, il compilatore cerca i nomi begine endnell'ambito della classe dell'oggetto su cui si itera, o guarda pronto per begin(range)eend(range) utilizzando la ricerca e li usa come iteratori argomento-dipendente.

Per ulteriori informazioni, nello standard C ++ 11 (o bozza pubblica dello stesso), "6.5.4 L' foristruzione basata su intervallo ", pag.145


4
Un "array dinamico" sarebbe quello creato con new[]. In tal caso, hai solo un puntatore senza indicazione della dimensione, quindi non c'è modo per il range-based fordi lavorare con esso.
Mike Seymour

La mia risposta include un array dinamico la cui dimensione (4) è nota al momento della compilazione, ma non so se questa interpretazione di "array dinamico" sia ciò che intendeva l'interrogante.
Johannes Schaub - litb

3

Come funziona il range-based for per gli array semplici?

È da leggere come, " Dimmi cosa fa un ranged-for (con gli array)? "

Risponderò supponendo che: prendi il seguente esempio utilizzando array annidati:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

Versione testo:

iaè un array di array ("array annidato"), contenente [3]array, ciascuno contenente [4]valori. L'esempio precedente scorre in iabase al suo "intervallo" principale ( [3]) e quindi esegue un ciclo di [3]volte. Ogni loop produce uno dei ia's [3]valori primari partire dal primo e termina con l'ultimo - matrice An contenenti [4]valori.

  • Primo ciclo: pluguale a {1,2,3,4}- Un array
  • Secondo ciclo: pluguale a {5,6,7,8}- Un array
  • Terzo ciclo: pluguale a {9,10,11,12}- Un array

Prima di spiegare il processo, ecco alcuni promemoria amichevoli sugli array:

  • Gli array vengono interpretati come puntatori al loro primo valore: l'utilizzo di un array senza alcuna iterazione restituisce l'indirizzo del primo valore
  • pl dovere essere un riferimento perché non possiamo copiare gli array
  • Con gli array, quando aggiungi un numero all'oggetto array stesso, avanza molte volte e 'punta' alla voce equivalente - Se nè il numero in questione, allora ia[n]è lo stesso di *(ia+n)(Stiamo dereferenziando l'indirizzo che è nvoci avanti) ed ia+nè uguale a &ia[n](Stiamo ottenendo l'indirizzo di quella voce nell'array).

Ecco cosa sta succedendo:

  • Su ogni ciclo, plè impostato come riferimento a ia[n], con nuguale al conteggio del ciclo corrente a partire da 0. Quindi, plè ia[0]sul primo giro, sul secondo è ia[1]e così via. Recupera il valore tramite iterazione.
  • Il ciclo continua finché ia+nè minore di end(ia).

... E questo è tutto.

È davvero solo un modo semplificato per scrivere questo :

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int n = 0; n != 3; ++n)
  auto &pl = ia[n];

Se il tuo array non è annidato, questo processo diventa un po 'più semplice in quanto non è necessario un riferimento , perché il valore iterato non è un array ma piuttosto un valore "normale":

 int ib[3] = {1,2,3};

 // short
 for (auto pl : ib)
   cout << pl;

 // long
 for (int n = 0; n != 3; ++n)
   cout << ib[n];

Alcune informazioni aggiuntive

E se non volessimo utilizzare la autoparola chiave durante la creazione pl? Come sarebbe?

Nell'esempio seguente, plfa riferimento a un file array of four integers. Ad ogni loop plviene assegnato il valore ia[n]:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int (&pl)[4] : ia)

E ... è così che funziona, con informazioni aggiuntive per spazzare via ogni confusione. È solo un forciclo "abbreviato" che conta automaticamente per te, ma non ha un modo per recuperare il ciclo corrente senza farlo manualmente.


@ Andy 9 volte su 10 il titolo è ciò che corrisponde in Google / qualunque ricerca - Il titolo chiede come funzionano? , non quando sa quando fermarsi? . Anche così, la domanda sottostante implicita è trattata in una certa misura in questa risposta e continua a rispondere per chiunque cerchi l' altra risposta. Domande di sintassi come queste dovrebbero avere titoli formulati in modo che una risposta possa essere scritta usando solo quello perché sono tutte le informazioni di cui il ricercatore ha bisogno per trovare la domanda. Certamente non hai torto: la domanda non è intitolata come dovrebbe essere.
Super Cat

0

Alcuni esempi di codice per dimostrare la differenza tra le matrici sullo Stack e le matrici sull'heap


/**
 * Question: Can we use range based for built-in arrays
 * Answer: Maybe
 * 1) Yes, when array is on the Stack
 * 2) No, when array is the Heap
 * 3) Yes, When the array is on the Stack,
 *    but the array elements are on the HEAP
 */
void testStackHeapArrays() {
  int Size = 5;
  Square StackSquares[Size];  // 5 Square's on Stack
  int StackInts[Size];        // 5 int's on Stack
  // auto is Square, passed as constant reference
  for (const auto &Sq : StackSquares)
    cout << "StackSquare has length " << Sq.getLength() << endl;
  // auto is int, passed as constant reference
  // the int values are whatever is in memory!!!
  for (const auto &I : StackInts)
    cout << "StackInts value is " << I << endl;

  // Better version would be: auto HeapSquares = new Square[Size];
  Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's it has
  // for (auto &Sq : HeapSquares)
  //    cout << "HeapSquare has length " << Sq.getLength() << endl;

  // does not compile, same reason as above
  // for (const auto &I : HeapInts)
  //  cout << "HeapInts value is " << I << endl;

  // Create 3 Square objects on the Heap
  // Create an array of size-3 on the Stack with Square pointers
  // size of array is known to compiler
  Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
  // auto is Square*, passed as constant reference
  for (const auto &Sq : HeapSquares2)
    cout << "HeapSquare2 has length " << Sq->getLength() << endl;

  // Create 3 int objects on the Heap
  // Create an array of size-3 on the Stack with int pointers
  // size of array is known to compiler
  int *HeapInts2[]{new int(23), new int(57), new int(99)};
  // auto is int*, passed as constant reference
  for (const auto &I : HeapInts2)
    cout << "HeapInts2 has value " << *I << endl;

  delete[] HeapSquares;
  delete[] HeapInts;
  for (const auto &Sq : HeapSquares2) delete Sq;
  for (const auto &I : HeapInts2) delete I;
  // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
}
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.