Perché usare iteratori invece di indici di array?


239

Prendi le seguenti due righe di codice:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

E questo:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Mi è stato detto che il secondo modo è preferito. Perché esattamente questo?


72
Il secondo modo è preferito è passare some_iterator++a ++some_iterator. Post-incremento crea un iteratore temporaneo non necessario.
Jason,

6
Dovresti anche end()inserire la clausola di dichiarazione.
Razze di leggerezza in orbita

5
@Tomalak: chiunque utilizzi un'implementazione C ++ con un inefficiente vector::endprobabilmente ha problemi peggiori di cui preoccuparsi rispetto al fatto che sia sollevato o meno dai loop. Personalmente preferisco la chiarezza - se fosse una chiamata findnelle condizioni di terminazione mi preoccuperei, comunque.
Steve Jessop,

13
@Tomalak: quel codice non è sciatto (beh, forse il post-incremento), è conciso e chiaro, per quanto gli iteratori C ++ consentano la concisione. L'aggiunta di più variabili aggiunge uno sforzo cognitivo per il bene di un'ottimizzazione prematura. È sciatto.
Steve Jessop,

7
@Tomalak: è prematuro se non è un collo di bottiglia. Il tuo secondo punto mi sembra assurdo, poiché il confronto corretto non è tra it != vec.end()e it != end, è tra (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)e (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). Non ho bisogno di contare i personaggi. Preferisci l'uno rispetto all'altro, ma il disaccordo delle altre persone con la tua preferenza non è "sciatteria", è una preferenza per un codice più semplice con meno variabili e quindi meno a cui pensare durante la lettura.
Steve Jessop,

Risposte:


210

Il primo modulo è efficiente solo se vector.size () è un'operazione veloce. Questo vale per i vettori, ma non per gli elenchi, ad esempio. Inoltre, cosa hai intenzione di fare all'interno del corpo del ciclo? Se prevedi di accedere agli elementi come in

T elem = some_vector[i];

quindi stai assumendo che il contenitore sia stato operator[](std::size_t)definito. Ancora una volta, questo è vero per il vettore ma non per altri contenitori.

L'uso di iteratori ti avvicina all'indipendenza del contenitore . Non stai facendo ipotesi sull'abilità di accesso casuale o sul size()funzionamento veloce , ma solo sul fatto che il contenitore ha funzionalità di iteratore.

È possibile migliorare ulteriormente il codice utilizzando algoritmi standard. A seconda di ciò che è si sta cercando di raggiungere, si può scegliere di utilizzare std::for_each(), std::transform()e così via. Usando un algoritmo standard anziché un ciclo esplicito, stai evitando di reinventare la ruota. È probabile che il tuo codice sia più efficiente (dato che viene scelto l'algoritmo giusto), corretto e riutilizzabile.


8
Inoltre, hai dimenticato che gli iteratori possono fare cose come essere fail-fast, quindi se c'è una modifica simultanea alla struttura a cui stai accedendo, lo saprai. Non puoi farlo con un solo numero intero.
Marcin,

4
Questo mi confonde: "Questo è vero per i vettori, ma non per gli elenchi, per esempio." Perché? Chiunque abbia un cervello manterrà una size_tvariabile membro che ne tiene traccia size().
GManNickG,

19
@GMan - in quasi tutte le implementazioni, size () è veloce per gli elenchi altrettanto per i vettori. La prossima versione dello standard richiederà che ciò sia vero. Il vero problema è la lentezza della risposta per posizione.
Daniel Earwicker,

8
@GMan: la memorizzazione della dimensione dell'elenco richiede che la suddivisione e la giunzione dell'elenco siano O (n) anziché O (1).

5
In C ++ 0x, la size()funzione membro dovrà avere una complessità temporale costante per tutti i contenitori che la supportano, incluso std::list.
James McNellis,

54

Fa parte del moderno processo di indottrinamento C ++. Gli iteratori sono l'unico modo per iterare la maggior parte dei contenitori, quindi lo usi anche con i vettori solo per entrare nella giusta mentalità. Seriamente, questa è l'unica ragione per cui lo faccio - non credo di aver mai sostituito un vettore con un diverso tipo di contenitore.


Caspita, questo è ancora sottovalutato dopo tre settimane. Immagino che non paghi per essere un po 'ironico.

Penso che l'indice di array sia più leggibile. Corrisponde alla sintassi utilizzata in altre lingue e alla sintassi utilizzata per le matrici C vecchio stile. È anche meno prolisso. L'efficienza dovrebbe essere un lavaggio se il tuo compilatore è buono e non ci sono quasi casi in cui è importante comunque.

Anche così, mi ritrovo ancora a usare iteratori frequentemente con i vettori. Credo che l'iteratore sia un concetto importante, quindi lo promuovo ogni volta che posso.


1
Anche gli iteratori C ++ sono terribilmente rotti concettualmente. Per i vettori, sono stato appena catturato perché il puntatore di fine è acutally end + 1 (!). Per i flussi il modello iteratore è semplicemente surreale, un token immaginario che non esiste. Allo stesso modo per gli elenchi collegati. Il paradigma ha senso solo per le matrici, e quindi non molto. Perché ho bisogno di due oggetti iteratore, non solo uno ...
Montabile il

5
@aberglas non sono affatto rotti, non ci sei abituato, motivo per cui io sostengo di usarli anche quando non è necessario! Le gamme semiaperte sono un concetto comune e le sentinelle alle quali non si intende mai accedere direttamente sono vecchie quanto la programmazione stessa.
Mark Ransom,

4
dai un'occhiata agli stream iteratori e pensa a cosa == è stato pervertito per adattarsi allo schema, e poi dimmi che gli iteratori non sono rotti! O per elenchi collegati. Anche per gli array, dover specificare uno oltre la fine è un'idea spezzata in stile C - puntatore a mai e poi mai. Dovrebbero essere come Java o C # o qualsiasi iteratore di qualsiasi altro linguaggio, con un iteratore richiesto (invece di due oggetti) e un semplice test finale.
Montabile il

53

perché non stai associando il tuo codice alla particolare implementazione dell'elenco some_vector. se usi indici di array, deve essere una forma di array; se usi iteratori puoi usare quel codice su qualsiasi implementazione di lista.


23
L'interfaccia std :: list non offre intenzionalmente l'operatore [] (size_t n) perché sarebbe O (n).
MSalters,

33

Immagina che some_vector sia implementato con un elenco collegato. Quindi la richiesta di un elemento nell'i-esimo posto richiede l'esecuzione di operazioni per attraversare l'elenco dei nodi. Ora, se usi iteratore, in generale, farà del suo meglio per essere il più efficiente possibile (nel caso di un elenco collegato, manterrà un puntatore al nodo corrente e lo farà avanzare in ogni iterazione, richiedendo solo un singola operazione).

Quindi fornisce due cose:

  • Astrazione d'uso: vuoi solo iterare alcuni elementi, non ti importa di come farlo
  • Prestazione

1
"manterrà un puntatore al nodo corrente e lo farà avanzare [roba buona sull'efficienza]" - sì, non capisco perché le persone abbiano difficoltà a comprendere il concetto di iteratori. sono concettualmente solo un superset di puntatori. perché calcolare ripetutamente l'offset di alcuni elementi quando è possibile memorizzare nella cache un puntatore ad esso? bene, è quello che fanno anche gli iteratori.
underscore_d

27

Sarò il difensore dei diavoli qui e non consiglierò gli iteratori. Il motivo principale per cui è tutto il codice sorgente su cui ho lavorato dallo sviluppo di applicazioni desktop allo sviluppo di giochi che non ho né ho avuto bisogno di usare iteratori. Per tutto il tempo in cui non sono stati richiesti e, in secondo luogo, le ipotesi nascoste, il disordine del codice e gli incubi di debug che si ottengono con gli iteratori li rendono un ottimo esempio per non utilizzarlo in qualsiasi applicazione che richieda velocità.

Anche dal punto di vista della manutenzione sono un disastro. Non è a causa loro, ma a causa di tutti gli alias che avvengono dietro la scena. Come faccio a sapere che non hai implementato il tuo vettore virtuale o elenco di array che fa qualcosa di completamente diverso dagli standard. So che tipo è attualmente in fase di esecuzione? Hai sovraccaricato un operatore Non ho avuto il tempo di controllare tutto il tuo codice sorgente. Devo sapere quale versione dell'STL stai usando?

Il prossimo problema che si presenta con gli iteratori è un'astrazione che perde, sebbene ci siano numerosi siti Web che ne discutono in dettaglio con loro.

Siamo spiacenti, non ho e non ho ancora visto alcun punto negli iteratori. Se astraggono la lista o il vettore lontano da te, quando in realtà dovresti già sapere con quale vettore o elenco stai gestendo se non lo fai, ti preparerai per alcune sessioni di debug in futuro.


23

È possibile che si desideri utilizzare un iteratore se si desidera aggiungere / rimuovere elementi nel vettore mentre si esegue l'iterazione su di esso.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Se si utilizzano gli indici, è necessario mescolare gli elementi su / giù nell'array per gestire gli inserimenti e le eliminazioni.


3
se si desidera inserire elementi nel mezzo del contenitore, forse un vettore non è una buona scelta per iniziare. ovviamente, torniamo al perché gli iteratori sono fantastici; è banale passare a un elenco.
Wilhelmtell,

L'iterazione su tutti gli elementi è piuttosto costosa in std::listconfronto a a std::vector, tuttavia, se si consiglia di utilizzare un elenco collegato anziché a std::vector. Vedi pagina 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf Nella mia esperienza, ho trovato un std::vectorè più veloce di un std::listanche se sto cercando su tutto questo e rimuovendo elementi in posizioni arbitrarie.
David Stone,

Gli indici sono stabili, quindi non vedo quale shuffling aggiuntivo è necessario per inserimenti ed eliminazioni.
musiphil,

... E con un elenco collegato - che è quello che dovrebbe essere in uso qui - la tua istruzione di ciclo sarebbe for (node = list->head; node != NULL; node = node->next)più corta delle tue prime due righe di codice messe insieme (dichiarazione e testa del ciclo). Quindi ripeto - non c'è molta differenza sostanziale nella brevità tra l'uso di iteratori e non il loro utilizzo - hai ancora soddisfatto le tre parti di una fordichiarazione, anche se usi while: dichiari, itera, controlla la terminazione.
Ingegnere

16

Separazione degli interessi

È molto bello separare il codice di iterazione dalla preoccupazione "core" del loop. È quasi una decisione di progettazione.

In effetti, iterando per indice ti lega all'implementazione del contenitore. Chiedere al contenitore un iteratore di inizio e fine, abilita il codice del ciclo per l'uso con altri tipi di contenitore.

Inoltre, a std::for_eachproposito, dici alla collezione cosa fare, invece di chiedergli qualcosa sui suoi interni

Lo standard 0x introdurrà chiusure, che renderanno questo approccio molto più facile da usare - dai un'occhiata al potere espressivo di ad esempio Ruby [1..6].each { |i| print i; }...

Prestazione

Ma forse un problema molto supervisionato è che, usando l' for_eachapproccio, si ottiene l'opportunità di parallelizzare l'iterazione: i blocchi di threading Intel possono distribuire il blocco di codice sul numero di processori nel sistema!

Nota: dopo aver scoperto la algorithmsbiblioteca, e in particolare foreach, ho passato due o tre mesi a scrivere strutture ridicolmente piccole di "aiutanti" che faranno impazzire i tuoi colleghi sviluppatori. Dopo questo tempo, sono tornato a un approccio pragmatico: i piccoli corpi ad anello non meritano foreachpiù :)

Un riferimento da leggere sugli iteratori è il libro "Extended STL" .

Il GoF ha un piccolo paragrafo alla fine del modello Iterator, che parla di questo marchio di iterazioni; si chiama "iteratore interno". Dai un'occhiata anche qui .


15

Perché è più orientato agli oggetti. se stai ripetendo un indice stai assumendo:

a) che quegli oggetti sono ordinati
b) che quegli oggetti possono essere ottenuti da un indice
c) che l'incremento dell'indice colpirà ogni elemento
d) che quell'indice inizia da zero

Con un iteratore, stai dicendo "dammi tutto in modo che io possa lavorare con esso" senza sapere quale sia l'implementazione sottostante. (In Java, ci sono raccolte a cui non è possibile accedere tramite un indice)

Inoltre, con un iteratore, non è necessario preoccuparsi di uscire dai limiti dell'array.


2
Non credo che "orientato agli oggetti" sia il termine corretto. Gli iteratori non sono "orientati agli oggetti" nel design. Promuovono la programmazione funzionale più della programmazione orientata agli oggetti, poiché incoraggiano a separare gli algoritmi dalle classi.
Wilhelmtell,

Inoltre, gli iteratori non aiutano a evitare di uscire dai limiti. Gli algoritmi standard lo fanno, ma gli iteratori da soli no.
Wilhelmtell,

Abbastanza giusto @wilhelmtell, sto ovviamente pensando a questo da un punto di vista incentrato su Java.
cinico

1
E penso che promuova OO, perché sta separando le operazioni sulle raccolte dall'implementazione di quella raccolta. Una raccolta di oggetti non dovrebbe necessariamente sapere quali algoritmi dovrebbero essere utilizzati per lavorare con essi.
cinico

In realtà ci sono versioni dell'STL che hanno verificato gli iteratori, il che significa che genererà una sorta di eccezione fuori limite quando si tenta di fare qualcosa con quell'iteratore.
Daemin,

15

Un'altra cosa bella degli iteratori è che ti permettono di esprimere (e applicare) la tua preferenza const. Questo esempio assicura che non modificherai il vettore durante il ciclo:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

Sembra ragionevole, ma dubito ancora che se questo è il motivo per averlo const_iterator. Se altero il vettore nel loop, lo faccio per una ragione e per il 99,9% delle volte che l'alterazione non è un incidente, e per il resto, è solo un bug come qualsiasi tipo di bug nel codice dell'autore deve risolvere. Perché in Java e in molte altre lingue, non esiste alcun oggetto const, ma gli utenti di quelle lingue non hanno mai problemi con nessun supporto const in quelle lingue.
Neevek,

2
@neevek Se questo non è il motivo per cui const_iterator, allora quale potrebbe essere il motivo?
underscore_d,

@underscore_d, mi chiedo anche io. Non sono esperto in questo, è solo che la risposta non è convincente per me.
Neevek,

15

A parte tutte le altre eccellenti risposte ... intpotrebbe non essere abbastanza grande per il tuo vettore. Invece, se si desidera utilizzare l'indicizzazione, utilizzare size_typeper il proprio contenitore:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@Pat Notz, questo è un ottimo punto. Nel corso del porting di un'applicazione Windows basata su STL su x64, ho dovuto gestire centinaia di avvertimenti sull'assegnazione di size_t a un int che potrebbe causare il troncamento.
bk1e,

1
Per non parlare del fatto che i tipi di dimensioni non sono firmati e int è firmato, in modo da avere non intuitivi, le conversioni bug-nascondigli in corso solo per confrontare int ia myvector.size().
Adrian McCarthy,

12

Probabilmente dovrei sottolineare che puoi anche chiamare

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

Gli iteratori STL sono principalmente presenti in modo che gli algoritmi STL come l'ordinamento possano essere indipendenti dal contenitore.

Se vuoi semplicemente passare in rassegna tutte le voci in un vettore, usa semplicemente lo stile del ciclo dell'indice.

È meno dattiloscritto e più facile da analizzare per la maggior parte degli umani. Sarebbe bello se C ++ avesse un semplice ciclo foreach senza esagerare con la magia dei template.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

Non penso che faccia molta differenza per un vettore. Preferisco usare un indice da solo poiché lo considero più leggibile e puoi fare un accesso casuale come saltare avanti di 6 elementi o saltare indietro se necessario.

Mi piace anche fare un riferimento all'elemento all'interno del loop in questo modo, quindi non ci sono molte parentesi quadre intorno al luogo:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

L'uso di un iteratore può essere utile se pensi che potresti dover sostituire il vettore con un elenco ad un certo punto in futuro e sembra anche più elegante per i maniaci di STL, ma non riesco a pensare a nessun altro motivo.


la maggior parte degli algoritmi opera una volta su ogni elemento di un contenitore, in sequenza. Ovviamente ci sono delle eccezioni in cui vorresti attraversare una collezione in un ordine o modo specifico, ma in questo caso farei di tutto per scrivere un algoritmo che si integri con l'STL e che funzioni con gli iteratori.
Wilhelmtell,

Ciò incoraggerebbe il riutilizzo ed eviterà in seguito errori off-by-one. Chiamerei quindi quell'algoritmo proprio come qualsiasi altro algoritmo standard, con iteratori.
Wilhelmtell,

1
Non ho nemmeno bisogno di anticipo (). L'iteratore ha gli stessi operatori + = e - = di un indice (per contenitori vettoriali e simili a vettori).
MSalters,

I prefer to use an index myself as I consider it to be more readablesolo in alcune situazioni; in altri, gli indici diventano rapidamente molto disordinati. and you can do random accessche non è affatto una caratteristica unica degli indici: vedi en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d

3

La seconda forma rappresenta ciò che stai facendo in modo più preciso. Nel tuo esempio, in realtà non ti interessa il valore di i - tutto ciò che vuoi è l'elemento successivo nell'iteratore.


3

Dopo aver appreso un po 'di più sull'argomento di questa risposta, mi rendo conto che si è trattato di una semplificazione eccessiva. La differenza tra questo ciclo:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

E questo ciclo:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

È abbastanza minimale. In effetti, la sintassi di fare loop in questo modo sembra crescere su di me:

while (it != end){
    //do stuff
    ++it;
}

Gli iteratori sbloccano alcune funzionalità dichiarative abbastanza potenti e, se combinati con la libreria di algoritmi STL, puoi fare cose piuttosto interessanti che esulano dall'ambito degli amministratori dell'indice di array.


La verità è che se tutti gli iteratori fossero compatti come il tuo esempio finale, appena usciti dalla confezione, avrei avuto pochi problemi con loro. Naturalmente, questo è in realtà uguale a for (Iter it = {0}; it != end; ++it) {...}- hai appena lasciato fuori la dichiarazione - quindi la brevità non è molto diversa dal tuo secondo esempio. Ancora, +1.
Ingegnere

3

L'indicizzazione richiede un'operazione aggiuntiva mul. Ad esempio, per vector<int> v, il compilatore si converte v[i]in &v + sizeof(int) * i.


Probabilmente non è uno svantaggio significativo rispetto agli iteratori nella maggior parte dei casi, ma è bene essere consapevoli.
nobar

3
Per accessi isolati a singolo elemento, probabilmente. Ma se stiamo parlando di loop - come lo era l'OP - allora sono abbastanza sicuro che questa risposta sia basata su un compilatore immaginario non ottimizzante. Ogni mezzo decente avrà ampie opportunità e probabilità di memorizzare nella cache sizeofe aggiungerlo una volta sola per iterazione, piuttosto che ripetere l'intero calcolo dell'offset ogni volta.
underscore_d

2

Durante l'iterazione non è necessario conoscere il numero di elementi da elaborare. Hai solo bisogno dell'oggetto e gli iteratori fanno queste cose molto bene.


2

Nessuno ha ancora detto che un vantaggio degli indici è che non diventano non validi quando si accoda a un contenitore contiguo come std::vector, quindi è possibile aggiungere elementi al contenitore durante l'iterazione.

Questo è possibile anche con gli iteratori, ma è necessario chiamare reserve()e quindi è necessario sapere quanti elementi aggiungere.


1

Diversi punti positivi già. Ho alcuni commenti aggiuntivi:

  1. Supponendo che stiamo parlando della libreria standard C ++, "vettore" implica un contenitore ad accesso casuale che ha le garanzie di C-array (accesso casuale, layout di memoria contigua, ecc.). Se avessi detto "some_container", molte delle risposte di cui sopra sarebbero state più precise (indipendenza del container ecc.).

  2. Per eliminare eventuali dipendenze dall'ottimizzazione del compilatore, è possibile spostare some_vector.size () fuori dal ciclo nel codice indicizzato, in questo modo:

    const size_t numElems = some_vector.size ();
    per (size_t i = 0; i 
  3. Pre-incrementa sempre gli iteratori e tratta i post-incrementi come casi eccezionali.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// do stuff}

Quindi, supponendo e indicizzabile std::vector<>come un contenitore, non vi è alcuna buona ragione per preferire l'uno all'altro, passando in sequenza attraverso il contenitore. Se devi fare spesso riferimento a indici elennenti più vecchi o più recenti, la versione indicizzata è più appropriata.

In generale, è preferibile utilizzare gli iteratori perché gli algoritmi ne fanno uso e il comportamento può essere controllato (e implicitamente documentato) modificando il tipo di iteratore. Le posizioni delle matrici possono essere utilizzate al posto degli iteratori, ma la differenza sintattica sporge.


1

Non uso iteratori per lo stesso motivo per cui non mi piacciono le dichiarazioni foreach. Quando si hanno più cicli interni è abbastanza difficile tenere traccia delle variabili globali / membri senza dover ricordare anche tutti i valori locali e i nomi degli iteratori. Ciò che trovo utile è usare due serie di indici per diverse occasioni:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Non voglio nemmeno abbreviare cose come "animation_matrices [i]" in qualche casuale "anim_matrix" chiamato ad esempio iteratore, perché quindi non puoi vedere chiaramente da quale array provenga questo valore.


Non vedo come gli indici siano migliori in questo senso. Si potrebbe facilmente utilizzare iteratori e basta scegliere una convenzione per i nomi: it, jt, kt, ecc o anche solo continuare a utilizzare i, j, k, ecc E se avete bisogno di sapere esattamente che cosa un iteratore rappresenta, poi a me una cosa del genere for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()sarebbe più descrittivo che dover indicizzare continuamente come animation_matrices[animIndex][boneIndex].
underscore_d,

wow, sembra che secoli fa ho scritto quell'opinione. oggigiorno si usano sia iteratori foreach che c ++ senza creare molto. Immagino che lavorare con il buggy code per anni costruisca la propria tolleranza, quindi è più facile accettare tutte le sintassi e le convenzioni ... fintanto che funziona e finché si può andare a casa si sa;)
AareP,

Ahah, davvero, non ho davvero guardato quanti anni avesse prima! Qualcos'altro a cui in qualche modo non ho pensato l'ultima volta è che al giorno d'oggi abbiamo anche il forloop basato su range , che rende il modo basato su iteratore di farlo ancora più conciso.
underscore_d

1
  • Se ti piace essere vicino al metal / non ti fidi dei loro dettagli di implementazione, non usare iteratori.
  • Se si cambia regolarmente un tipo di raccolta per un altro durante lo sviluppo, utilizzare iteratori.
  • Se trovi difficile ricordare come iterare diversi tipi di raccolte (forse hai diversi tipi da diverse fonti esterne in uso), usa gli iteratori per unificare i mezzi con cui cammini sugli elementi. Questo vale per dire passare da un elenco collegato a un elenco di array.

Davvero, è tutto quello che c'è da fare. Non è come ottenere una maggiore brevità in entrambi i modi in media e se la brevità è davvero il tuo obiettivo, puoi sempre ricorrere alle macro.


1

Se hai accesso alle funzionalità di C ++ 11 , puoi anche utilizzare un ciclo basatofor sull'intervallo per iterare sul tuo vettore (o qualsiasi altro contenitore) come segue:

for (auto &item : some_vector)
{
     //do stuff
}

Il vantaggio di questo ciclo è che puoi accedere agli elementi del vettore direttamente tramite la itemvariabile, senza correre il rischio di incasinare un indice o fare un errore quando si dereferenzia un iteratore. Inoltre, il segnaposto autoti impedisce di ripetere il tipo di elementi contenitore, il che ti avvicina ancora di più a una soluzione indipendente dal contenitore.

Appunti:

  • Se hai bisogno dell'indice dell'elemento nel tuo ciclo e di quello operator[]esistente per il tuo contenitore (ed è abbastanza veloce per te), allora meglio andare per la tua prima strada.
  • forNon è possibile utilizzare un ciclo basato sull'intervallo per aggiungere / eliminare elementi in / da un contenitore. Se vuoi farlo, allora atteniti alla soluzione data da Brian Matthews.
  • Se non si desidera modificare gli elementi nel contenitore, quindi si dovrebbe utilizzare la parola chiave constcome segue: for (auto const &item : some_vector) { ... }.

0

Ancora meglio di "dire alla CPU cosa fare" (imperativo) è "dire alle librerie cosa vuoi" (funzionale).

Quindi, invece di usare i loop, dovresti imparare gli algoritmi presenti in stl.



0

Uso sempre l'indice di array perché molte mie applicazioni richiedono qualcosa di simile a "display thumbnail image". Quindi ho scritto qualcosa del genere:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

Entrambe le implementazioni sono corrette, ma preferirei il ciclo "for". Dato che abbiamo deciso di utilizzare un vettore e non qualsiasi altro contenitore, l'utilizzo degli indici sarebbe l'opzione migliore. L'uso di iteratori con i vettori perderebbe il vantaggio di avere gli oggetti in blocchi di memoria continui che ne facilitano l'accesso.


2
"L'uso di iteratori con i vettori perderebbe il vantaggio di avere gli oggetti in blocchi di memoria continui che ne facilitano l'accesso." [citazione necessaria]. Perché? Pensi che un incremento di un iteratore in un contenitore contiguo non possa essere implementato come semplice aggiunta?
underscore_d

0

Ho sentito che nessuna delle risposte qui spiega perché mi piacciono gli iteratori come concetto generale rispetto all'indicizzazione in contenitori. Nota che la maggior parte della mia esperienza con gli iteratori non proviene in realtà dal C ++ ma da linguaggi di programmazione di livello superiore come Python.

L'interfaccia iteratore impone meno requisiti ai consumatori della tua funzione, il che consente ai consumatori di fare di più con esso.

Se tutto ciò che serve è essere in grado di inoltrare-iterazioni, lo sviluppatore non si limita all'utilizzo di contenitori intercambiabili - possono utilizzare qualsiasi classe che implementa operator++(T&), operator*(T)e operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

Il tuo algoritmo funziona nel caso in cui ne hai bisogno - iterando su un vettore - ma può anche essere utile per applicazioni che non prevedi necessariamente:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

Tentare di implementare un operatore tra parentesi quadre che fa qualcosa di simile a questo iteratore sarebbe inventato, mentre l'implementazione dell'iteratore è relativamente semplice. L'operatore parentesi quadre ha anche implicazioni sulle capacità della tua classe - che puoi indicizzare su qualsiasi punto arbitrario - che può essere difficile o inefficiente da implementare.

Gli iteratori si prestano anche alla decorazione . Le persone possono scrivere iteratori che prendono un iteratore nel loro costruttore e ne estendono la funzionalità:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

Mentre questi giocattoli possono sembrare banali, non è difficile immaginare di usare iteratori e decoratori di iteratori per fare cose potenti con una semplice interfaccia: decorare un iteratore solo in avanti dei risultati del database con un iteratore che costruisce un oggetto modello da un singolo risultato, ad esempio . Questi schemi consentono l'iterazione efficiente di memoria di insiemi infiniti e, con un filtro come quello che ho scritto sopra, valutazione potenzialmente pigra dei risultati.

Parte della potenza dei modelli C ++ è la tua interfaccia iteratore, quando applicata a maglie simili di array C a lunghezza fissa, decade in aritmetica puntatore semplice ed efficiente , rendendola un'astrazione a costo zero.

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.