Scelta del tipo di variabili indice


11

Usiamo il tipo intero che rappresenta le variabili indice per la maggior parte del tempo. Ma in alcune situazioni, siamo costretti a scegliere

std::vector<int> vec;
....

for(int i = 0; i < vec.size(); ++i)
....

Ciò farà sì che il compilatore sollevi l'avvertimento che l'uso misto di variabili firmate / non firmate. se faccio l'indice variabile come for( size_t i = 0; i < vec.size(); i++ ), (o un unsigned int) risolverà i problemi.

Quando diventa più specifico usare i tipi di Windows, la maggior parte delle API di Windows hanno a che fare con DWORD (che è stato tipizzato come unsigned long).

Quindi, quando utilizzo un'iterazione simile, causerà nuovamente lo stesso avviso. Ora se lo riscrivo come

DWORD dwCount;
....

for(DWORD i = 0; i < dwCount; ++i)
....

Lo trovo un po 'strano. Potrebbe essere il problema con le percezioni.

Concordo sul fatto che dovremmo utilizzare lo stesso tipo di variabile indice per evitare che si possano verificare problemi di intervallo con le variabili indice. Per esempio se stiamo usando

_int64 i64Count; // 
....

for(_int64 i = 0; i < i64Count; ++i)
....

Ma nel caso di DWORD, o numeri interi senza segno, ci sono problemi a riscriverlo come

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

In che modo la maggior parte delle persone lavora con problemi simili?


4
Perché dovresti usare un numero intero con segno per rappresentare l'indice? È come usare il vettore di numeri interi per memorizzare una stringa.
Šimon Tóth,

3
@Let_Me_Be: perché semplifica il test delle condizioni al contorno. Ad esempio, con un conto alla rovescia fino a zero, un test minore di prima di eseguire il corpo del ciclo non può funzionare con un valore senza segno. Allo stesso modo un conteggio fino al massimo non funziona. Naturalmente in quel caso non funzionerà nemmeno un intero con segno (perché non può rappresentare un valore così grande).
Yttrill

"Questo farà sì che il compilatore sollevi l'avvertimento che l'uso misto di variabili firmate / non firmate." Questo è solo uno dei due problemi che dovrai affrontare. In molti casi std::size_tè un rango più alto di int (o addirittura lungo). Se la dimensione del vettore supera mai std::numeric_limits<int>::max(), ti pentirai di aver usato int.
Adrian McCarthy,

Risposte:


11

vector ha un typedef che ti dice il tipo corretto da usare: -

for(std::vector<int>::size_type i = 0; i < thing.size(); ++i)
{
}

È quasi sempre definito come size_t, ma non ci si può fare affidamento


8
Non proprio un miglioramento nella leggibilità, IMHO.
Doc Brown,

No, ma poiché è l'unico modo per dire quale sia il tipo corretto da utilizzare per un indice nel vettore, questo non ha molta importanza ...
JohnB

4
In questi giorni in c ++ 11 basta usare auto
JohnB

6
@JohnB Vuoi dire come auto i = 0? Questo non aiuta affatto, idiventa un int.
Zenith,

1
La leggibilità può essere migliorata con using index_t = std::vector<int>::size_type;.
Toby Speight,

4
std::vector<int> vec;

for(int i = 0; i < vec.size(); ++i)

Usa un iteratore per questo, non un forloop.

Per gli altri, purché il tipo di variabile abbia le stesse dimensioni, static_castdovrebbe funzionare bene (ovvero DWORDa int16_t)


2
for (std::vector<int>::iterator i = vec.begin(); i != vec.end(); ++i)è un dolore da scrivere. Avere for (auto i = vec.begin();...è molto più leggibile. Certo, foreachè anche in C ++ 11.
David Thornley,

3

Il caso che hai descritto è anche una delle cose che non mi piacciono in C ++. Ma ho imparato a conviverci, sia usando

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

o

for( int i = 0; i < (int)vec.size(); i++ )

(ovviamente, quest'ultimo solo quando non c'è rischio di ottenere un po 'di overflow).


3

Il motivo per cui ti avvisa del confronto tra firmato e non firmato è perché il valore firmato verrà probabilmente convertito in non firmato, il che potrebbe non essere quello che ti aspetti.

Nel tuo esempio (rispetto inta size_t), intverrà convertito implicitamente in size_t(a meno che in intqualche modo non abbia un intervallo maggiore di size_t). Pertanto, se intè negativo, sarà probabilmente maggiore del valore a cui lo stai confrontando a causa di avvolgimento. Questo non sarà un problema se il tuo indice non è mai negativo, ma riceverai comunque questo avviso.

Invece, utilizzare un tipo unsigned (come ad esempio unsigned int, size_to, come John B raccomanda , std::vector<int>::size_type) per la variabile index:

for(unsigned int i = 0; i < vec.size(); i++)

Fai attenzione quando conto alla rovescia, tuttavia:

for(unsigned int i = vec.size()-1; i >= 0; i--) // don't do this!

Quanto sopra non funzionerà perché i >= 0è sempre vero quando iè senza segno. Utilizzare invece "l' operatore freccia " per i loop che contano:

for (unsigned int i = vec.size(); i-- > 0; )
    vec[i] = ...;

Come indicano altre risposte, normalmente si desidera utilizzare un iteratore per attraversare a vector. Ecco la sintassi di C ++ 11:

for (auto i = vec.begin(); i != vec.end(); ++i)

1
Ciò comporta ancora il rischio che un unsigned intnon sia abbastanza grande da contenere le dimensioni.
Adrian McCarthy,

2

Una nuova opzione per C ++ 11, puoi fare cose come le seguenti

for(decltype(vec.size()) i = 0; i < vec.size(); ++i) {...}

e

for(decltype(dWord) i = 0; i < dWord; ++i) {...}

Mentre si ripete un po 'più di quanto non farebbe il ciclo for-base di base, non è così prolisso come i modi pre-'11 per specificare i valori, e l'utilizzo di questo modello funzionerà coerentemente per la maggior parte, se non per tutti, i possibili termini che avresti voglio confrontare, il che lo rende ottimo per il refactoring del codice. Funziona anche con casi semplici come questo:

int x = 3; int final = 32; for(decltype(final) i = x; i < final; ++i)

Inoltre, mentre dovresti usare autoogni volta che stai impostando iun valore intelligente (come vec.begin()), decltypefunziona quando stai impostando una costante come zero, dove auto lo risolverebbe semplicemente intperché 0 è un semplice numero intero.

Ad essere sincero, mi piacerebbe vedere un meccanismo di compilazione per estendere la autodeterminazione del tipo per gli incrementatori di loop per esaminare il valore confrontato.


1

Uso un cast per int, come in for (int i = 0; i < (int)v.size(); ++i). Sì, è brutto. Lo biasimo per lo stupido design della libreria standard in cui decisero di usare numeri interi senza segno per rappresentare le dimensioni. (Al fine di ... cosa? Estendere l'intervallo di un bit?)


1
In quale situazione sarebbe significativa una dimensione della raccolta di qualcosa di negativo? L'uso di numeri interi senza segno per varie dimensioni di raccolte mi sembra la scelta ragionevole . L'ultima volta che ho controllato, prendendo la lunghezza di una stringa raramente ha restituito un risultato negativo o ...
un CVn

1
In quale situazione sarebbe significativo per un semplice test tale da if(v.size()-1 > 0) { ... }restituire true per un contenitore vuoto? Il problema è che le dimensioni sono spesso utilizzate anche in aritmetica, esp. con contenitori basati su indice, che richiede problemi dato che non sono firmati. Fondamentalmente, l'utilizzo di tipi senza segno per nient'altro che 1) manipolazioni bit per bit o 2) l'aritmetica modulare richiede problemi.
zvrba,

2
buon punto. Anche se non vedo davvero il punto del tuo esempio particolare (probabilmente scriverei solo if(v.size() > 1) { ... }perché ciò rende l'intento più chiaro e, come bonus aggiuntivo, il problema del firmato / non firmato diventa nullo), vedo come in alcuni casi specifici la firma potrebbe essere utile. Sono corretto.
un CVn

1
@Michael: sono d'accordo che è stato un esempio inventato. Tuttavia, scrivo spesso algoritmi con loop nidificati: for (i = 0; i <v.size () - 1; ++ i) for (j = i + 1; j <v.size (); ++ j) .. se v è vuoto, il ciclo esterno viene eseguito (size_t) -1 volte. Quindi o devo controllare v.empty () prima del loop o lanciare v.size () su un tipo firmato, entrambi i quali penso personalmente che siano brutti rimedi. Scelgo un cast in quanto è meno LOC, no if () s => meno possibilità di errore. (Inoltre, nel secondo complemento, oveflow di conversione restituisce un numero negativo, quindi il ciclo non viene eseguito affatto.)
zvrba

L'estensione dell'intervallo di 1 bit è stato (e continua ad essere) molto utile nei sistemi a 16 bit.
Adrian McCarthy,
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.