Iterazione su std :: vector: variabile indice senza segno vs firmata


470

Qual è il modo corretto di iterare su un vettore in C ++?

Considera questi due frammenti di codice, questo funziona benissimo:

for (unsigned i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

e questo:

for (int i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

che genera warning: comparison between signed and unsigned integer expressions.

Sono nuovo nel mondo del C ++, quindi la unsignedvariabile mi sembra un po 'spaventosa e so che le unsignedvariabili possono essere pericolose se non utilizzate correttamente, quindi - è corretto?


10
Quello senza segno è corretto perché polygon.size () è di tipo unsigned. Unsigned significa sempre positivo o 0. Questo è tutto. Quindi, se l'uso della variabile è sempre solo per i conteggi, allora unsigned è la scelta giusta.
Adam Bruss,

3
@AdamBruss .size()non è di tipo unsignedaka unsigned int. È di tipo std::size_t.
underscore_d

1
@underscore_d size_t è un alias per unsigned.
Adam Bruss,

2
@AdamBruss No. std::size_tè un typedef definito da _implementation. Vedi lo standard. std::size_tpotrebbe essere equivalente unsignedall'implementazione corrente, ma non è pertinente. Fingere che sia può comportare un codice non portatile e un comportamento indefinito.
underscore_d

2
@LF ... certo, che è probabilmente std::size_tin pratica. Pensi che abbiamo coperto tutto ancora in questo flusso sconclusionato di commenti per oltre 6 anni?
underscore_d

Risposte:


817

Per iterare all'indietro vedi questa risposta .

L'iterazione in avanti è quasi identica. Basta cambiare gli iteratori / decrementare lo swap con incrementi. Dovresti preferire gli iteratori. Alcune persone ti dicono di usare std::size_tcome tipo di variabile indice. Tuttavia, questo non è portatile. Usa sempre il size_typetypedef del contenitore (Mentre potresti cavartela solo con una conversione nel caso di iterazione in avanti, in realtà potrebbe andare storto nel caso di iterazione all'indietro quando lo usi std::size_t, nel caso std::size_tsia più largo di quello che è il typedef di size_type) :


Utilizzando std :: vector

Utilizzo di iteratori

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    /* std::cout << *it; ... */
}

È importante utilizzare sempre il modulo di incremento del prefisso per gli iteratori di cui non si conoscono le definizioni. Ciò assicurerà che il tuo codice sia il più generico possibile.

Utilizzo dell'intervallo C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Utilizzando gli indici

for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
    /* std::cout << v[i]; ... */
}

Utilizzo di array

Utilizzo di iteratori

for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
    /* std::cout << *it; ... */
}

Utilizzo dell'intervallo C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Utilizzando gli indici

for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
    /* std::cout << a[i]; ... */
}

sizeofTuttavia, leggi nella risposta iterante all'indietro a quale problema può comportare l' approccio.


tipo di dimensione dei puntatori: l'utilizzo del tipo di differenza potrebbe essere più portabile. prova iterator_traits <element_type *> ::ference_type. questa è una boccata di una dichiarazione, ma è più portatile ...
Wilhelmtell,

Wilhelmtell, per cosa dovrei usare differenza_tipo? sizeof è definito per restituire size_t :) non ti capisco. se dovessi sottrarre i puntatori gli uni dagli altri, differenza_tipo sarebbe la scelta giusta.
Johannes Schaub - litb

l'iterazione su array utilizzando la tecnica menzionata in questo post non funzionerà se l'iterazione viene eseguita in una funzione su un array passato a quella funzione. Perché array sizeof restituirà solo il puntatore sizeof.
systemfault

1
@Nils sono d'accordo che usare contatori di loop senza segno sia una cattiva idea. ma poiché la libreria standard utilizza tipi interi senza segno per indice e dimensione, preferisco tipi di indice senza segno per la libreria standard. altre librerie di conseguenza usano solo tipi firmati, come la libreria Qt.
Johannes Schaub - lit

32
Aggiornamento per C ++ 11: intervallo basato per loop. for (auto p : polygon){sum += p;}
Siyuan Ren,

170

Sono passati quattro anni, Google mi ha dato questa risposta. Con lo standard C ++ 11 (aka C ++ 0x ) esiste in realtà un nuovo modo piacevole di farlo (al prezzo di rompere la compatibilità con le versioni precedenti): la nuova autoparola chiave. Ti risparmia il dolore di dover specificare esplicitamente il tipo di iteratore da utilizzare (ripetendo nuovamente il tipo di vettore), quando è ovvio (per il compilatore), quale tipo usare. Con l' vessere tuo vector, puoi fare qualcosa del genere:

for ( auto i = v.begin(); i != v.end(); i++ ) {
    std::cout << *i << std::endl;
}

C ++ 11 va ancora oltre e ti offre una sintassi speciale per iterare raccolte come vettori. Elimina la necessità di scrivere cose sempre uguali:

for ( auto &i : v ) {
    std::cout << i << std::endl;
}

Per vederlo in un programma di lavoro, crea un file auto.cpp:

#include <vector>
#include <iostream>

int main(void) {
    std::vector<int> v = std::vector<int>();
    v.push_back(17);
    v.push_back(12);
    v.push_back(23);
    v.push_back(42);
    for ( auto &i : v ) {
        std::cout << i << std::endl;
    }
    return 0;
}

Al momento della stesura di questo, quando lo compili con g ++ , normalmente devi impostarlo per funzionare con il nuovo standard dando un flag extra:

g++ -std=c++0x -o auto auto.cpp

Ora puoi eseguire l'esempio:

$ ./auto
17
12
23
42

Nota che le istruzioni per la compilazione e l'esecuzione sono specifiche del compilatore gnu c ++ su Linux , il programma dovrebbe essere indipendente dalla piattaforma (e dal compilatore).


7
C ++ 11 ti dàfor (auto& val: vec)
Flexo

@flexo Grazie, non so come potrei dimenticarlo. Non sto facendo abbastanza C ++, immagino. Non riuscivo a credere che ci fosse qualcosa di così pratico (pensavo che fosse in realtà la sintassi JavaScript). Ho cambiato la risposta per includerla.
Kratenko l'

La tua risposta è molto bella È spiacevole che la versione predefinita di g ++ in vari devkit del sistema operativo sia sotto 4.3, il che non lo fa funzionare.
Ratata Tata,

Devi inizializzare il vettore con std::vector<int> v = std::vector<int>();, o avresti potuto semplicemente usare std::vector<int> v;invece?
Bill Cheatham,

@BillCheatham Beh, l'ho appena provato senza l'inizializzazione, e ha funzionato, quindi sembra che funzioni senza.
Kratenko,

44

Nel caso specifico del tuo esempio, utilizzerei gli algoritmi STL per ottenere questo risultato.

#include <numeric> 

sum = std::accumulate( polygon.begin(), polygon.end(), 0 );

Per un caso più generale, ma comunque abbastanza semplice, sceglierei:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );

38

Per quanto riguarda la risposta di Johannes Schaub:

for(std::vector<T*>::iterator it = v.begin(); it != v.end(); ++it) { 
...
}

Ciò potrebbe funzionare con alcuni compilatori ma non con gcc. Il problema qui è la domanda se std :: vector :: iterator è un tipo, una variabile (membro) o una funzione (metodo). Otteniamo il seguente errore con gcc:

In member function void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant

La soluzione sta usando la parola chiave 'typename' come detto:

typename std::vector<T*>::iterator it = v.begin();
for( ; it != v.end(); ++it) {
...

2
Dovresti capire che questo vale solo quando Tè un argomento template, e quindi l'espressione std::vector<T*>::iteratorè un nome dipendente. Perché un nome dipendente venga analizzato come tipo, deve essere anteposto dalla typenameparola chiave, come indica la diagnostica.
Ripristina Monica il

17

Una chiamata a vector<T>::size()restituisce un valore di tipo std::vector<T>::size_type, non int, unsigned int o altrimenti.

Anche in genere l'iterazione su un contenitore in C ++ viene eseguita utilizzando iteratori , come questo.

std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();

for(; i != end; i++){
    sum += *i;
}

Dove T è il tipo di dati archiviati nel vettore.

O utilizzando diversi algoritmi di iterazione ( std::transform, std::copy, std::fill, std::for_eacheccetera).


Gli iteratori sono generalmente una buona idea, anche se dubito che sia necessario memorizzare "end" in una variabile separata e tutto può essere fatto all'interno di un'istruzione for (;;).
Saulius Žemaitaitis,

1
So che begin () e end () sono ammortizzati a tempo costante, ma generalmente trovo che questo sia più leggibile che stipare tutto in una riga.
Jasper Bekkers,

3
È possibile dividere il per in righe separate per migliorare la leggibilità. Dichiarare gli iteratori all'esterno del ciclo significa che è necessario un nome iteratore diverso per ogni ciclo su contenitori di tipi diversi.
Jay Conrod

Sono consapevole di tutte le differenze e ciò che sostanzialmente si riduce è la preferenza personale; questo è generalmente il modo in cui finisco per fare le cose.
Jasper Bekkers,

2
@pihentagy Immagino che sarebbe quello di impostarlo nella prima sezione del for-loop. per esempio. per (auto i = polygon.begin (), end = polygon.end (); i! = end; i ++)
Jasper Bekkers

11

Utilizzare size_t:

for (size_t i=0; i < polygon.size(); i++)

Citando Wikipedia :

I file di intestazione stdlib.h e stddef.h definiscono un tipo di dati chiamato size_tche viene utilizzato per rappresentare la dimensione di un oggetto. Le funzioni di libreria che assumono dimensioni si aspettano che siano di tipo size_te la dimensione dell'operatore valuta size_t.

Il tipo effettivo di size_tdipende dalla piattaforma; un errore comune è assumere che size_tsia uguale a unsigned int, il che può portare a errori di programmazione, in particolare quando le architetture a 64 bit diventano più diffuse.


size_t OK per il vettore, poiché deve memorizzare tutti gli oggetti in un array (anch'esso un oggetto) ma un elenco std :: può contenere più di elementi size_t!
MSalter

1
size_t è normalmente sufficiente per enumerare tutti i byte nello spazio degli indirizzi di un processo. Mentre posso vedere come questo potrebbe non essere il caso di alcune architetture esotiche, preferirei non preoccuparmene.

Si consiglia AFAIK #include <cstddef>piuttosto che <stddef.h>, o peggio, la totalità [c]stdlibe l'uso std::size_tpiuttosto che la versione non qualificata - e lo stesso per qualsiasi altra situazione in cui è possibile scegliere tra <cheader>e <header.h>.
underscore_d

7

Un po 'di storia:

Per indicare se un numero è negativo o no, utilizzare un bit "segno". intè un tipo di dati con segno che significa che può contenere valori positivi e negativi (da circa -2 a 2 miliardi di miliardi). Unsignedpuò solo memorizzare numeri positivi (e poiché non spreca un po 'sui metadati, può memorizzare più: da 0 a circa 4 miliardi).

std::vector::size()restituisce un unsigned, come può un vettore avere una lunghezza negativa?

L'avvertimento ti dice che l'operando di destra della tua dichiarazione di disuguaglianza può contenere più dati di quello di sinistra.

In sostanza, se hai un vettore con più di 2 miliardi di voci e usi un numero intero per indicizzare, colpirai i problemi di overflow (l'int ritornerà a 2 miliardi negativi).


6

Di solito uso BOOST_FOREACH:

#include <boost/foreach.hpp>

BOOST_FOREACH( vector_type::value_type& value, v ) {
    // do something with 'value'
}

Funziona su contenitori STL, array, stringhe in stile C, ecc.


2
Buona risposta a qualche altra domanda (come devo ripetere un vettore?), Ma non è affatto quello che l'OP stava chiedendo (qual è il significato dell'avvertimento su una variabile non firmata?)
abelenky,

3
Bene, ha chiesto quale fosse il modo corretto di iterare su un vettore. Quindi sembra abbastanza pertinente. L'avvertimento è solo perché non è contento della sua soluzione attuale.
jalf

5

Per essere completo, la sintassi C ++ 11 abilita solo un'altra versione per gli iteratori ( ref ):

for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
  // do something with *it
}

Che è anche comodo per l'iterazione inversa

for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
  // do something with *it
}

5

In C ++ 11

Vorrei usare algoritmi generali come for_eachper evitare la ricerca del giusto tipo di iteratore ed espressione lambda per evitare funzioni / oggetti extra nominati.

L'esempio "carino" per il tuo caso particolare (supponendo che il poligono sia un vettore di numeri interi):

for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });

testato su: http://ideone.com/i6Ethd

Non dimenticare di includere: algoritmo e, naturalmente, vettore :)

Microsoft ha anche un bell'esempio su questo:
fonte: http://msdn.microsoft.com/en-us/library/dd293608.aspx

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
   // Create a vector object that contains 10 elements.
   vector<int> v;
   for (int i = 1; i < 10; ++i) {
      v.push_back(i);
   }

   // Count the number of even numbers in the vector by 
   // using the for_each function and a lambda.
   int evenCount = 0;
   for_each(v.begin(), v.end(), [&evenCount] (int n) {
      cout << n;
      if (n % 2 == 0) {
         cout << " is even " << endl;
         ++evenCount;
      } else {
         cout << " is odd " << endl;
      }
   });

   // Print the count of even numbers to the console.
   cout << "There are " << evenCount 
        << " even numbers in the vector." << endl;
}

4
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 

2
Per il vettore questo va bene, ma in genere è meglio usarlo ++ piuttosto che ++, nel caso in cui l'iteratore stesso non sia banale.
Steve Jessop,

Personalmente, sono abituato a usare ++ i, ma penso che la maggior parte delle persone preferisca lo stile i ++ (lo snippet di codice VS predefinito per "for" è i ++). Solo un pensiero
Mehrdad Afshari,

@MehrdadAfshari A chi importa cosa fa la "maggior parte delle persone"? "la maggior parte delle persone" ha torto su molte cose. Il post-inc / decremento in cui il valore pre non viene mai usato è sbagliato e inefficiente, almeno in teoria - indipendentemente dalla frequenza in cui viene usato ciecamente nel codice di esempio sotto-par ovunque. Non dovresti incoraggiare le cattive pratiche solo per far sembrare le cose più familiari alle persone che non conoscono ancora meglio.
underscore_d

2

Il primo è di tipo corretto e corretto in un certo senso. (Se ci pensi, la dimensione non può mai essere inferiore a zero.) Questo avvertimento mi sembra uno dei buoni candidati per essere ignorato, però.


2
Penso che sia un terribile candidato da ignorare: è facile da risolvere e ogni tanto si verificano veri e propri bug a causa di errori nel confrontare i valori firmati / non firmati in modo inappropriato. Ad esempio, in questo caso, se la dimensione è maggiore di INT_MAX, il ciclo non termina mai.
Steve Jessop,

... o forse termina immediatamente. Uno dei due. Dipende se il valore con segno viene convertito in non firmato per il confronto o se il segno senza segno viene convertito in firmato. Su una piattaforma a 64 bit con un int a 32 bit, tuttavia, come win64, l'int verrebbe promosso a size_t e il ciclo non finirà mai.
Steve Jessop,

@SteveJessop: non puoi dire con certezza che il ciclo non finisce mai. Sull'iterazione quando i == INT_MAX, quindi i++provoca un comportamento indefinito. A questo punto può succedere di tutto.
Ben Voigt,

@BenVoigt: vero, e ancora non fornisce motivi per ignorare l'avvertimento :-)
Steve Jessop,

2

Valuta se devi ripetere l'iterazione

L' <algorithm>intestazione standard ci fornisce le funzionalità per questo:

using std::begin;  // allows argument-dependent lookup even
using std::end;    // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);

Altre funzioni nella libreria degli algoritmi eseguono attività comuni: assicurati di sapere cosa è disponibile se vuoi risparmiare.


1

Dettaglio oscuro ma importante: se dici "per (auto esso)" come segue, otterrai una copia dell'oggetto, non l'elemento reale:

struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.push_back(x);
for(auto it : v)
    it.i = 1;         // doesn't change the element v[0]

Per modificare gli elementi del vettore, è necessario definire l'iteratore come riferimento:

for(auto &it : v)

1

Se il compilatore lo supporta, è possibile utilizzare un intervallo basato su per accedere agli elementi vettoriali:

vector<float> vertices{ 1.0, 2.0, 3.0 };

for(float vertex: vertices){
    std::cout << vertex << " ";
}

Stampe: 1 2 3. Nota, non puoi usare questa tecnica per cambiare gli elementi del vettore.


0

I due segmenti di codice funzionano allo stesso modo. Tuttavia, route "unsigned int" è corretta. L'uso dei tipi int senza segno funzionerà meglio con il vettore nell'istanza in cui è stato utilizzato. Chiamare la funzione del membro size () su un vettore restituisce un valore intero senza segno, quindi si desidera confrontare la variabile "i" per un valore del proprio tipo.

Inoltre, se sei ancora un po 'a disagio su come "unsigned int" appare nel tuo codice, prova "uint". Questa è fondamentalmente una versione abbreviata di "unsigned int" e funziona esattamente allo stesso modo. Inoltre non è necessario includere altre intestazioni per usarlo.


Un numero intero senza segno per size () non è necessariamente uguale a "int senza segno" in termini di C ++, spesso "numero intero senza segno" in questo caso è un numero intero senza segno a 64 bit mentre "numero senza segno" è di solito a 32 bit.
Medran

0

Aggiungendo questo come non riuscivo a trovarlo menzionato in nessuna risposta: per l'iterazione basata su indice, possiamo usare decltype(vec_name.size())ciò che valuterebbestd::vector<T>::size_type

Esempio

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
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.