2011-09-20 15 views
35

Questa è una domanda piuttosto stupida, ma perché è int comunemente utilizzato invece di unsigned int quando si definisce un ciclo for per un array in C o C++?Perché int è piuttosto che unsigned int utilizzato per cicli C e C++?

for(int i;i<arraySize;i++){} 
for(unsigned int i;i<arraySize;i++){} 

riconosco i vantaggi di utilizzare int quando si fa qualcosa di diverso schieramento indicizzazione ed i benefici di un iteratore quando si usano contenitori C++. È solo perché non importa quando si esegue il looping di un array? O dovrei evitare tutto insieme e usare un tipo diverso come size_t?

+15

È meno da scrivere. – slartibartfast

+0

Inoltre: più leggibile – Alex

+0

È come mai non chiami altre persone con il nome e il cognome per tutto il tempo, ma semplicemente chiamali con il loro nome? – user482594

risposta

29

Questo è un fenomeno più generale, spesso le persone non utilizzano i tipi corretti per i loro numeri interi. La C moderna ha typedef semantici che sono molto preferibili rispetto ai tipi interi primitivi. Ad esempio, tutto ciò che è una "dimensione" dovrebbe essere digitato come size_t. Se si utilizzano sistematicamente i tipi semantici per le variabili dell'applicazione, le variabili di ciclo risultano molto più semplici anche con questi tipi.

E ho visto diversi bug che risultavano difficili da rilevare provenire dall'uso di int o giù di lì. Codice che all'improvviso si è schiantato su grandi matrici e cose del genere. Basta codificare correttamente con i tipi corretti per evitarlo.

+5

Il tipo corretto per una dimensione è 'size_t', sfortunatamente' size_t' è stato definito utilizzando un tipo errato stesso (senza segno) e questa è la fonte di un gran numero di bug. Io preferisco usare tipi semanticamente corretti per il codice (ad esempio 'int') piuttosto che usare tipi formalmente corretti ma semanticamente sbagliati. Con 'int's puoi incorrere in bug per valori molto grandi (incredibilmente grandi) ... con valori' unsigned' il comportamento pazzo è molto più vicino all'uso quotidiano (0). – 6502

+1

@ 6502, le opinioni sembrano variare molto su questo. Potresti dare un'occhiata al mio post sul blog: http://gustedt.wordpress.com/2013/07/15/a-praise-of-size_t-and-other-unsigned-types/ –

+2

@JensGustedt: che il la semantica è sbagliata non è un'opinione, a meno che tu pensi che sia corretto che 'a.size() - b.size()' debba essere di circa quattro miliardi quando 'b' ha un elemento e' a' non ne ha.Che qualcuno pensi che "non firmato" sia un'idea fantastica per i numeri non negativi, hai ragione, ma la mia impressione è che abbiano attribuito troppo peso al nome piuttosto che al vero significato. Tra quelli che pensano che unsigned sia una cattiva idea per i contatori e gli indici è Bjarne Stroustrup ... vedi http://stackoverflow.com/q/10168079/320726 – 6502

0

Io uso int perché richiede una minore digitazione fisica e non importa: occupa la stessa quantità di spazio e, a meno che l'array disponga di alcuni miliardi di elementi, non si verificherà un overflow se non si utilizza un Compilatore a 16 bit, che di solito non sono.

+5

Non usare un 'int' dà anche più contesto sulla variabile e può essere considerato come un codice di auto-documentazione. Leggi anche qui: http://www.viva64.com/en/a/0050/ –

4

Non c'è molta differenza. Un vantaggio di int è la firma. Pertanto, lo int i < 0 ha senso, mentre lo unsigned i < 0 non ha molto.

Se gli indici sono calcolati, ciò può essere utile (ad esempio, potresti ottenere casi in cui non entrerai mai in un ciclo se un risultato è negativo).

E sì, è meno di scrivere :-)

+0

'typedef unsigned us;' ed è più da scrivere. –

+3

@WTP - sei uno di quelli che non capiranno il sarcasmo anche con ":-)" proprio accanto ad esso? Beh, immagino che non ci sia cura ... – littleadv

+0

Una dimensione negativa o un indice negativo non ha senso –

2

Utilizzando int per indicizzare un array è un'eredità, ma ancora ampiamente adottato. int è solo un tipo di numero generico e non corrisponde alle capacità di indirizzamento della piattaforma. Nel caso in cui sia più corto o più lungo, potresti riscontrare strani risultati quando cerchi di indicizzare un array molto grande che va oltre.

Su piattaforme moderne, off_t, ptrdiff_t e size_t garantiscono molto più portabilità.

Un altro vantaggio di questi tipi è che forniscono contesto a qualcuno che legge il codice. Quando vedi i tipi di cui sopra, sai che il codice eseguirà l'indice dell'array o l'aritmetica del puntatore, non solo qualsiasi calcolo.

Quindi, se si desidera scrivere codice a prova di proiettile, portatile e sensibile al contesto, è possibile farlo a scapito di alcuni tasti.

GCC supporta anche un'estensione typeof, che si solleva da digitare lo stesso typename tutto il luogo:

typeof(arraySize) i; 

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

Quindi, se si cambia il tipo di arraySize, il tipo di i cambia automaticamente.

+2

Anche se per essere giusti, su tutte le piattaforme a 32 e 64 bit più oscure, avresti bisogno di almeno 4 miliardi di elementi per tali problemi da mostrare. E le piattaforme con 'int's più piccoli hanno in genere una memoria molto meno, rendendo' int' sufficiente in generale. – delnan

+1

@delnan: Non è così semplice. Questo tipo di ragionamento ha portato a vulnerabilità molto gravi in ​​passato, anche da gente che pensa a se stessa come un dio della sicurezza come DJB ... –

0

Dipende molto dal programmatore. Alcuni programmatori preferiscono il perfezionismo del tipo, quindi useranno qualsiasi tipo di confronto.Ad esempio, se stanno scorrendo una stringa C, si potrebbe vedere:

size_t sz = strlen("hello"); 
for (size_t i = 0; i < sz; i++) { 
    ... 
} 

Mentre se stanno solo facendo qualcosa di 10 volte, probabilmente ancora vedere int:

for (int i = 0; i < 10; i++) { 
    ... 
} 
0

Perché se non si dispone di un array con dimensioni superiori a due gigabyte di tipo char o 4 gigabyte di tipo short o 8 gigabyte di tipo int, non importa se la variabile è firmata o meno.

Quindi, perché digitare di più quando è possibile digitare di meno?

+1

Ma poi, se 'arraySize' è variabile e vuoi scrivere un codice a prova di proiettile,' off_t', 'ptrdiff_t' e' size_t' hanno ancora un significato. –

+0

Sì, questo è assolutamente necessario se POTETE avere array così enormi, ma dal momento che normalmente le persone non lo fanno, usano semplicemente "int" di semplice scrittura. Ad esempio, se stai ordinando un array di 'int' con O (n^2), devi sostanzialmente aspettare che l'array sia ordinato se ci sono più di 2M elementi, che è dato se hai anche 8GB di memoria. Quindi vedi, di solito anche se fai l'indicizzazione giusta, la maggior parte dei programmi è inutile quando viene dato un input così grande. Allora perché renderli a prova di proiettile? – Shahbaz

+0

@Shahbaz: La maggior parte di noi lo troverebbe sfortunato se passare un array gigante di questo tipo richieda settimane per essere completato, ma lo troverebbe del tutto inaccettabile quando il passaggio di un gigantesco array produce una shell di root. –

0

A parte il problema che è più breve da digitare, il motivo è che consente numeri negativi.

Dal momento che non possiamo dire in anticipo se un valore può mai essere negativo, la maggior parte delle funzioni che prendono argomenti interi prendono la varietà firmata. Poiché la maggior parte delle funzioni utilizza interi con segno, è spesso meno utile utilizzare numeri interi con segno per cose come loops. Altrimenti, hai il potenziale di dover aggiungere un po 'di typecasts.

Mentre ci spostiamo su piattaforme a 64 bit, l'intervallo senza segno di un numero intero con segno dovrebbe essere più che sufficiente per la maggior parte degli scopi. In questi casi, non c'è molto motivo per non utilizzare un numero intero con segno.

+0

I valori negativi sono un punto chiave e la tua è l'unica risposta che fa menzione di più di un token. Purtroppo, purtroppo ci sono conversioni standard implicite tra tipi di parametri firmati e non firmati, il che significa che il loro mixaggio può solo farcire piuttosto che lo scenario scomodo ma sicuro che descrivi di "dover aggiungere un sacco di caratteri tipografici". E "Mentre ci spostiamo su piattaforme a 64 bit, l'intervallo senza segno di un intero con segno ..." non è in realtà in crescita per la maggior parte dei compilatori/sistemi operativi - 'int's tende ancora ad essere a 32 bit, con lo spostamento di' long's da 32 a 64. –

4

È puramente pigrizia e ignoranza. Dovresti sempre usare i tipi giusti per gli indici e, a meno che tu non abbia ulteriori informazioni che limitano la gamma di possibili indici, size_t è il tipo giusto.

Ovviamente se la dimensione è stata letta da un campo a byte singolo in un file, allora si sa che è nell'intervallo 0-255 e int sarebbe un tipo di indice perfettamente ragionevole. Allo stesso modo, int andrebbe bene se si esegue il ciclo un numero fisso di volte, come da 0 a 99. Ma c'è ancora un altro motivo per non utilizzare int: se si utilizza i%2 nel corpo del loop per trattare gli indici pari/dispari in modo diverso, i%2 è molto più costoso quando i è firmato rispetto a quando è i senza segno ...

+1

vedere # 3 sulla mia risposta, non è "puramente" pigrizia/ignoranza – chacham15

+3

Che doesn ' t cambiare il fatto che il codice è sbagliato. Ecco un modo per risolverlo: 'for (size_t i = 10; i -> 0;)' –

31

utilizzando int è più corretto dal punto di vista logico per indicizzare una matrice.

unsigned semantico in C e C++ non significa "non negativo" ma è più simile a "maschera di bit" o "intero modulo".

Per comprendere il motivo per cui unsigned non è un buon tipo per un numero "non negativo" perche

  • Aggiunta di un intero possibilmente negativo ad un intero non negativo si ottiene un intero non negativo
  • la differenza di due numeri interi non negativi è sempre un numero intero non negativo
  • Moltiplicando un intero non negativo per un numero intero negativo si ottiene un risultato non negativo

Ovviamente nessuna delle frasi sopra ha senso ... ma è come C e C++ unsigned semantico funziona davvero.

Utilizzare effettivamente un tipo unsigned per la dimensione dei contenitori è un errore di progettazione di C++ e purtroppo ora siamo condannati a utilizzare questa scelta errata per sempre (per compatibilità con le versioni precedenti). Potrebbe piacerti il ​​nome "unsigned" perché è simile a "non-negative" ma il nome è irrilevante e ciò che conta è il semantico ... e unsigned è molto lontano da "non-negativo".

Per questo motivo quando codifica maggior anelli su vettori mia forma personalmente preferito è:

for (int i=0,n=v.size(); i<n; i++) { 
    ... 
} 

(ovviamente assumendo la dimensione del vettore non cambia durante l'iterazione e che ho effettivamente bisogno l'indice nella corpo come altrimenti il ​​for (auto& x : v)... è migliore).

Questo allontanamento da unsigned appena possibile e l'utilizzo di numeri interi semplici ha il vantaggio di evitare le trap che sono una conseguenza dell'errore di progettazione unsigned size_t. Ad esempio si consideri:

// draw lines connecting the dots 
for (size_t i=0; i<pts.size()-1; i++) { 
    drawLine(pts[i], pts[i+1]); 
} 

il codice sopra avrà problemi se il vettore pts è vuoto perchè pts.size()-1 è un numero enorme dialogo in quel caso. Trattare con espressioni in cui lo a < b-1 non è lo stesso di a+1 < b anche per valori comunemente usati è come ballare in un campo minato.

Storicamente la giustificazione per avere size_t non firmato è per poter utilizzare il bit extra per i valori, ad es. essere in grado di avere 65535 elementi negli array anziché solo 32767 su piattaforme a 16 bit. A mio parere, anche in quel momento il costo extra di questa scelta semantica sbagliata non valeva il guadagno (e se 32767 elementi non sono sufficienti ora 65535 non sarà comunque sufficiente per molto tempo).

I valori non firmati sono grandi e molto utili, ma NON per rappresentare la dimensione del contenitore o per gli indici; per dimensioni e indice gli interi con segno regolare funzionano molto meglio perché la semantica è ciò che ti aspetteresti.

I valori senza segno sono il tipo ideale quando è necessaria la proprietà aritmetica modulo o quando si desidera lavorare a livello di bit.

+1

Penso che tu abbia ragione perché java (un C++ "migliorato") non supporta int unsigned. Inoltre penso che il modo corretto di scrivere quella riga sia: size_t arr_index; per (size_t i = 1; i <= pts.size(); i ++) { \t arr_index = i - 1; } – carlos

+2

@carlos: No. Questo ** sarebbe ** il modo corretto se 'size_t' sarebbe stato definito correttamente. Sfortunatamente un errore di progettazione ha reso 'size_t' un' unsigned' e quindi quei valori hanno finito per avere semantica bitmask. A meno che non pensiate che sia corretto che la dimensione di un contenitore sia una maschera di bit, usare 'size_t' è la scelta sbagliata. Una scelta che purtroppo è stata fatta dalla libreria standard del C++, ma nessuno mi obbliga a ripetere lo stesso errore nel mio codice. Il mio suggerimento è quello di scappare da 'size_t' e usare i normali int i appena possibile invece di accoppiare la logica in modo che funzioni anche con' size_t'. – 6502

+2

Non sono solo le piattaforme a 16 bit. Con l'attuale 'size_t' puoi usare ad es. 'vector ' di dimensioni ad es. 2.1G su IA-32 Linux con suddivisione di memoria 3G/1G. Se 'size_t' è stato firmato, cosa succederebbe se aumentassi il tuo vettore da <2G ad un altro? Improvvisamente le dimensioni diventeranno negative. Questo non ha alcun senso. La lingua non dovrebbe imporre tali limiti artificiali. – Ruslan

0

consideri il seguente esempio semplice:

int max = some_user_input; // or some_calculation_result 
for(unsigned int i = 0; i < max; ++i) 
    do_something; 

Se max sembra essere un valore negativo, diciamo -1, il -1 saranno considerati UINT_MAX (quando due interi con il rango sam ma segno diverso-ità sono confrontati, quello firmato sarà trattato come uno non firmato).D'altra parte, il seguente codice non avrebbe questo problema:

int max = some_user_input; 
for(int i = 0; i < max; ++i) 
    do_something; 

Dare un negativo max ingresso, il ciclo sarà saltato in modo sicuro.