Auswahl des Typs der Indexvariablen

11

Wir verwenden die meiste Zeit den Integer-Typ, der Indexvariablen darstellt. Aber in einigen Situationen sind wir gezwungen zu wählen

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

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

Dies führt dazu, dass der Compiler die Warnung auslöst, dass vorzeichenbehaftete / vorzeichenlose Variablen gemischt verwendet werden. Wenn ich die Indexvariable als for( size_t i = 0; i < vec.size(); i++ )(oder an unsigned int) mache , werden die Probleme behoben.

Wenn es spezifischer wird, Windows-Typen zu verwenden, befassen sich die meisten Windows-APIs mit DWORD (das als lange ohne Vorzeichen definiert wurde).

Wenn ich also eine ähnliche Iteration verwende, wird wieder dieselbe Warnung ausgelöst. Nun, wenn ich es umschreibe als

DWORD dwCount;
....

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

Ich finde das ein bisschen komisch. Es könnte das Problem mit den Wahrnehmungen sein.

Ich bin damit einverstanden, dass wir denselben Typ von Indexvariablen verwenden sollen, um zu vermeiden, dass bei den Indexvariablen Bereichsprobleme auftreten können. Zum Beispiel wenn wir verwenden

_int64 i64Count; // 
....

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

Im Fall von DWORD oder vorzeichenlosen Ganzzahlen gibt es jedoch Probleme beim Umschreiben als

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

Wie die meisten Leute arbeiten mit ähnlichen Themen?

Sarat
quelle
4
Warum sollten Sie eine vorzeichenbehaftete Ganzzahl verwenden, um den Index darzustellen? Das ist wie die Verwendung eines Vektors von ganzen Zahlen zum Speichern einer Zeichenfolge.
Let_Me_Be
3
@Let_Me_Be: Weil es das Testen der Randbedingungen erleichtert. Beispielsweise kann bei einem Countdown bis Null ein Test, der kleiner als ist, bevor der Schleifenkörper ausgeführt wird, nicht mit einem vorzeichenlosen Wert arbeiten. Ebenso funktioniert eine Zählung bis zum Maximum nicht. In diesem Fall funktioniert eine vorzeichenbehaftete Ganzzahl natürlich auch nicht (weil sie keinen so großen Wert darstellen kann).
Yttrill
"Dies führt dazu, dass der Compiler die Warnung auslöst, dass vorzeichenbehaftete / vorzeichenlose Variablen gemischt verwendet werden." Das ist nur eines der beiden Probleme, mit denen Sie zu kämpfen haben. In vielen Fällen std::size_tist ein höherer Rang als int (oder sogar lang). Wenn die Größe des Vektors jemals überschritten wird std::numeric_limits<int>::max(), werden Sie es bereuen, int verwendet zu haben.
Adrian McCarthy

Antworten:

11

vector hat ein typedef, das Ihnen den richtigen Typ angibt, der verwendet werden soll: -

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

Es wird fast immer als size_t definiert, aber darauf können Sie sich nicht verlassen

JohnB
quelle
8
Nicht wirklich eine Verbesserung der Lesbarkeit, IMHO.
Doc Brown
Nein, aber da dies der einzige Weg ist, um
festzustellen
4
In diesen Tagen in C ++ 11 verwenden Sie einfach Auto
JohnB
6
@ JohnB Du meinst wie auto i = 0? Das hilft überhaupt nicht, iein zu werden int.
Zenit
1
Die Lesbarkeit kann mit verbessert werden using index_t = std::vector<int>::size_type;.
Toby Speight
4
std::vector<int> vec;

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

Verwenden Sie dazu einen Iterator, keine forSchleife.

Für die anderen sollte, solange der Variablentyp dieselbe Größe hat, static_casteinwandfrei funktionieren (dh DWORDbis int16_t)

Demian Brecht
quelle
2
for (std::vector<int>::iterator i = vec.begin(); i != vec.end(); ++i)ist ein Schmerz zu schreiben. Haben for (auto i = vec.begin();...ist viel besser lesbar. Natürlich foreachist auch in C ++ 11.
David Thornley
3

Der von Ihnen beschriebene Fall ist eines der Dinge, die ich auch in C ++ nicht mag. Aber ich habe gelernt, damit zu leben, entweder indem ich es benutze

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

oder

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

(Letzteres natürlich nur, wenn kein Risiko eines int-Überlaufs besteht).

Doc Brown
quelle
3

Der Grund, warum Sie vor einem Vergleich zwischen signiert und nicht signiert gewarnt werden, liegt darin, dass der signierte Wert wahrscheinlich in einen nicht signierten Wert konvertiert wird, was möglicherweise nicht Ihren Erwartungen entspricht.

In Ihrem Beispiel (im Vergleich intzu size_t) intwird implizit in konvertiert size_t(es sei denn, es hat intirgendwie einen größeren Bereich als size_t). Wenn das intalso negativ ist, ist es wahrscheinlich größer als der Wert, mit dem Sie es vergleichen, aufgrund des Umlaufs. Dies ist kein Problem, wenn Ihr Index niemals negativ ist, aber Sie erhalten trotzdem diese Warnung.

Verwenden Sie stattdessen einen nicht signierten Typen (wie unsigned int, size_toder, wie John B empfiehlt , std::vector<int>::size_type) für Ihre Indexvariable:

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

Seien Sie jedoch vorsichtig, wenn Sie herunterzählen:

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

Das obige funktioniert nicht, da i >= 0es immer wahr ist, wenn ies nicht signiert ist. Verwenden Sie stattdessen den " Pfeiloperator " für Schleifen, die herunterzählen:

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

Wie andere Antworten zeigen, möchten Sie normalerweise einen Iterator verwenden, um a zu durchlaufen vector. Hier ist die C ++ 11-Syntax:

for (auto i = vec.begin(); i != vec.end(); ++i)
Joey Adams
quelle
1
Dies birgt immer noch das Risiko, dass ein unsigned intnicht groß genug ist, um die Größe zu halten.
Adrian McCarthy
2

Als neue Option für C ++ 11 können Sie folgende Aktionen ausführen

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

und

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

Es wiederholt sich zwar etwas mehr als die grundlegende for-Schleife, ist aber bei weitem nicht so langwierig wie die Angabe von Werten vor '11, und die konsequente Verwendung dieses Musters funktioniert für die meisten, wenn nicht alle möglichen Begriffe, die Sie möchten Ich möchte mit vergleichen, was es großartig für Code-Refactoring macht. Es funktioniert sogar für einfache Fälle wie diesen:

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

Während Sie autoimmer dann verwenden sollten, wenn Sie ieinen intelligenten Wert (wie vec.begin()) decltypefestlegen , funktioniert dies, wenn Sie eine Konstante wie Null festlegen, wobei auto dies nur auflösen würde, intda 0 ein einfaches ganzzahliges Literal ist.

Um ehrlich zu sein, würde ich gerne einen Compiler-Mechanismus sehen, mit dem die autoTypbestimmung für Schleifeninkrementierer erweitert werden kann, um den verglichenen Wert zu betrachten.

matthias
quelle
1

Ich benutze eine Besetzung für int, wie in for (int i = 0; i < (int)v.size(); ++i). Ja, es ist hässlich. Ich beschuldige das dumme Design der Standardbibliothek, in der sie beschlossen haben, vorzeichenlose Ganzzahlen zur Darstellung von Größen zu verwenden. (Um .. was? Den Bereich um ein Bit zu erweitern?)

zvrba
quelle
1
In welcher Situation wäre eine Sammlungsgröße von Negativ irgendetwas sinnvoll? Die Verwendung von Ganzzahlen ohne Vorzeichen für verschiedene Sammlungsgrößen scheint mir die vernünftige Wahl zu sein. Zuletzt habe ich überprüft, dass die Länge eines Strings auch selten ein negatives Ergebnis liefert ...
ein CVn
1
In welcher Situation wäre es für einen einfachen Test sinnvoll if(v.size()-1 > 0) { ... }, für einen leeren Container true zurückzugeben? Das Problem ist, dass Größen auch häufig in der Arithmetik verwendet werden, insb. mit indexbasierten Containern, die Probleme verursachen, da sie nicht signiert sind. Grundsätzlich ist die Verwendung von vorzeichenlosen Typen für etwas anderes als 1) bitweise Manipulationen oder 2) modulare Arithmetik problematisch.
Zvrba
2
guter Punkt. Obwohl ich den Sinn Ihres speziellen Beispiels nicht wirklich sehe (ich würde wahrscheinlich nur schreiben, if(v.size() > 1) { ... }da dies die Absicht klarer macht und als zusätzlicher Bonus die Ausgabe von signiert / nicht signiert ungültig wird), sehe ich in bestimmten Fällen, wie Signiertheit könnte nützlich sein. Ich stehe korrigiert.
ein CVn
1
@ Michael: Ich stimme zu, es war ein erfundenes Beispiel. Trotzdem schreibe ich oft Algorithmen mit verschachtelten Schleifen: für (i = 0; i <v.size () - 1; ++ i) für (j = i + 1; j <v.size (); ++ j) .. wenn v leer ist, wird die äußere Schleife -1-mal (size_t) ausgeführt. Also muss ich entweder v.empty () vor der Schleife überprüfen oder v.size () in einen signierten Typ umwandeln, was ich persönlich für hässliche Problemumgehungen halte. Ich wähle eine Besetzung, da es weniger LOC ist, nein wenn () s => weniger Möglichkeiten für Fehler. (Auch im 2. Komplement gibt Conversion Oveflow eine negative Zahl zurück, sodass die Schleife überhaupt nicht ausgeführt wird.)
Zvrba
Die Erweiterung des Bereichs um 1 Bit war (und ist) in 16-Bit-Systemen sehr nützlich.
Adrian McCarthy