Warum definiert der Standard end()
als am Ende vorbei, statt am eigentlichen Ende?
Warum sind Standard-Iterator-Bereiche [begin, end) instead of [begin, end]?
Hündchen
Kerrek SB
Das beste Argument ist einfach das von Dijkstra selbst:
-
Sie möchten, dass die Größe des Bereichs ein einfacher Unterschied ist Ende − Start;
-
Das Einbeziehen der unteren Grenze ist “natürlicher”, wenn Folgen zu leeren degenerieren, und auch weil die Alternative (ausschließlich die untere Grenze) würde die Existenz eines “Eins-vor-dem-Anfang”-Kennwerts erfordern.
Sie müssen immer noch begründen, warum Sie bei Null und nicht bei Eins zu zählen beginnen, aber das war nicht Teil Ihrer Frage.
Die Weisheit hinter dem [begin, end) convention pays off time and again when you have any sort of algorithm that deals with multiple nested or iterated calls to range-based constructions, which chain naturally. By contrast, using a doubly-closed range would incur off-by-ones and extremely unpleasant and noisy code. For example, consider a partition [n0, n1)[n1, n2)[n2,n3). Another example is the standard iteration loop for (it = begin; it != end; ++it)
, which runs end - begin
times. The corresponding code would be much less readable if both ends were inclusive – and imagine how you’d handle empty ranges.
Finally, we can also make a nice argument why counting should start at zero: With the half-open convention for ranges that we just established, if you are given a range of N elements (say to enumerate the members of an array), then 0 is the natural “beginning” so that you can write the range as [0, N), without any awkward offsets or corrections.
In a nutshell: the fact that we don’t see the number 1
everywhere in range-based algorithms is a direct consequence of, and motivation for, the [begin, end) convention.
-
The typical C for loop iterating over an array of size N is “for(i=0;i<N;i++) a[i]=0;”. Nun, Sie können das nicht direkt mit Iteratoren ausdrücken – viele Leute haben Zeit damit verschwendet, < sinnvoll zu machen. Aber es ist fast genauso offensichtlich, "for(i=0;i!=N;i++)" zu sagen. ..“ Die Zuordnung von 0 zum Anfang und N zum Ende ist daher praktisch.
– Krazy Glew
5. April 2012 um 16:31 Uhr
-
@KrazyGlew: Ich habe nicht absichtlich Typen in mein Schleifenbeispiel eingefügt. Wenn Sie denken
begin
undend
alsint
s mit Werten0
undN
bzw. es passt perfekt. Wohl ist es die!=
Zustand, der natürlicher ist als der traditionelle<
aber wir haben das nie entdeckt, bis wir anfingen, über allgemeinere Sammlungen nachzudenken.– Kerrek SB
5. April 2012 um 20:20 Uhr
-
@KerrekSB: Ich stimme zu, dass “wir das nie entdeckt haben [!= is better] bis wir anfingen, über allgemeinere Sammlungen nachzudenken.” IMHO ist das eines der Dinge, für die Stepanov Anerkennung verdient – als jemand, der versucht hat, solche Vorlagenbibliotheken vor der STL zu schreiben. Ich werde jedoch argumentieren, dass “!=” natürlicher ist – oder besser gesagt, ich werde argumentieren, dass != wahrscheinlich Fehler eingeführt hat, die < abfangen würde. Denken Sie an (i=0;i!=100;i+=3) ...
– Krazy Glew
6. April 2012 um 21:39 Uhr
-
@KrazyGlew: Ihr letzter Punkt ist etwas abseits des Themas, da die Sequenz {0, 3, 6, …, 99} nicht die Form hat, nach der das OP gefragt hat. Wenn Sie es so wollten, sollten Sie a schreiben
++
-Inkrementierbare Iteratorvorlagestep_by<3>
die dann die ursprünglich beworbene Semantik haben würde.– Kerrek SB
8. April 2012 um 11:00 Uhr
-
@KrazyGlew Auch wenn < mal einen Fehler verstecken würde, es ist sowieso ein bug. Wenn jemand verwendet
!=
wann er verwenden soll<
dann es ist ein Käfer. Übrigens ist dieser König der Fehler mit Unit-Tests oder Behauptungen leicht zu finden.– Phil1970
12. Mai 2017 um 3:55 Uhr
celtschk
Tatsächlich macht eine Menge iteratorbezogener Dinge plötzlich viel mehr Sinn, wenn Sie bedenken, dass die Iteratoren nicht zeigen bei die Elemente der Sequenz aber zwischen, wobei die Dereferenzierung auf das nächste Element direkt daneben zugreift. Dann macht der “one past end”-Iterator plötzlich sofort Sinn:
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
Offensichtlich begin
zeigt auf den Anfang der Sequenz, und end
zeigt auf das Ende derselben Sequenz. Dereferenzierung begin
greift auf das Element zu A
und Dereferenzierung end
macht keinen Sinn, weil es kein richtiges Element gibt. Außerdem wird ein Iterator hinzugefügt i
in der Mitte gibt
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^ ^
| | |
begin i end
und Sie sehen sofort, dass die Palette der Elemente aus begin
zu i
enthält die Elemente A
und B
während die Palette der Elemente aus i
zu end
enthält die Elemente C
und D
. Dereferenzierung i
ergibt das Element rechts davon, also das erste Element der zweiten Folge.
Sogar das “off-by-one” für Reverse-Iteratoren wird auf diese Weise plötzlich offensichtlich: Die Umkehrung dieser Sequenz ergibt:
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
Ich habe die entsprechenden nicht umgekehrten (Basis-) Iteratoren unten in Klammern geschrieben. Sie sehen, der Reverse-Iterator gehört dazu i
(die ich benannt habe ri
) still Punkte zwischen Elementen B
und C
. Aufgrund der Umkehrung der Reihenfolge, jetzt Element B
steht rechts daneben.
-
Dies ist IMHO die beste Antwort, obwohl ich denke, dass es besser veranschaulicht werden könnte, wenn die Iteratoren auf Zahlen zeigen würden und die Elemente zwischen den Zahlen wären (die Syntax
foo[i]
) ist eine Abkürzung für das Element sofort nach dem Positioni
). Wenn ich darüber nachdenke, frage ich mich, ob es für eine Sprache nützlich sein könnte, separate Operatoren für “Element unmittelbar nach Position i” und “Element unmittelbar vor Position i” zu haben, da viele Algorithmen mit Paaren benachbarter Elemente arbeiten und sagen: ” Die Elemente auf beiden Seiten der Position i” können sauberer sein als “Die Elemente an den Positionen i und i+1”.– Superkatze
22. Mai 2015 um 18:01 Uhr
-
@supercat: Die Zahlen sollten keine Iteratorpositionen / -indizes angeben, sondern die Elemente selbst angeben. Ich werde die Zahlen durch Buchstaben ersetzen, um das klarer zu machen. In der Tat, mit den angegebenen Zahlen,
begin[0]
(unter der Annahme eines Iterators mit wahlfreiem Zugriff) würde auf das Element zugreifen1
da es kein Element gibt0
in meiner Beispielsequenz.– Celtschk
17. September 2015 um 17:00 Uhr
-
Warum wird das Wort „begin“ statt „start“ verwendet? Schließlich ist „beginnen“ ein Verb.
– Benutzer1741137
1. Oktober 2019 um 11:42 Uhr
-
@ user1741137 Ich denke, “Beginn” soll die Abkürzung von “Beginn” sein (was jetzt Sinn macht). “Beginn” ist zu lang, “Beginn” klingt wie eine gute Passform. “start” würde mit dem Verb “start” kollidieren (z. B. wenn Sie eine Funktion definieren müssen
start()
in Ihrer Klasse zum Starten eines bestimmten Prozesses oder was auch immer, es wäre ärgerlich, wenn es mit einem bereits vorhandenen kollidiert).– Fareanor
24. Juni 2020 um 12:50 Uhr
Alok Speichern
Warum definiert der Standard end()
als am Ende vorbei, statt am eigentlichen Ende?
Weil:
- Es vermeidet eine spezielle Handhabung für leere Bereiche. Für leere Bereiche,
begin()
ist gleich
end()
& - Es macht das Endekriterium für Schleifen, die über die Elemente iterieren, einfach: Die Schleifen werden einfach fortgesetzt, solange
end()
wird nicht erreicht.
Benutzer541686
Weil dann
size() == end() - begin() // For iterators for whom subtraction is valid
und Sie müssen nicht tun unangenehm Dinge wie
// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }
und Sie werden nicht versehentlich schreiben fehlerhafter Code wie
bool empty() { return begin() == end() - 1; } // a typo from the first version
// of this post
// (see, it really is confusing)
bool empty() { return end() - begin() == -1; } // Signed/unsigned mismatch
// Plus the fact that subtracting is also invalid for many iterators
Ebenfalls: Was würde find()
zurückgeben, wenn end()
zeigte auf ein gültiges Element?
Tust du Ja wirklich will Ein weiterer Mitglied angerufen invalid()
was einen ungültigen Iterator zurückgibt?!
Zwei Iteratoren sind schon schmerzhaft genug …
Oh und sehen Das dazugehöriger Beitrag.
Ebenfalls:
Wenn die end
war vor dem letzten Element, wie würden Sie insert()
Am wahren Ende?!
Ken Bloom
Das Iterator-Idiom halbgeschlossener Bereiche [begin(), end())
is originally based on pointer arithmetic for plain arrays. In that mode of operation, you would have functions that were passed an array and a size.
void func(int* array, size_t size)
Converting to half-closed ranges [begin, end)
is very simple when you have that information:
int* begin;
int* end = array + size;
for (int* it = begin; it < end; ++it) { ... }
To work with fully-closed ranges, it’s harder:
int* begin;
int* end = array + size - 1;
for (int* it = begin; it <= end; ++it) { ... }
Since pointers to arrays are iterators in C++ (and the syntax was designed to allow this), it’s much easier to call std::find(array, array + size, some_value)
than it is to call std::find(array, array + size - 1, some_value)
.
Plus, if you work with half-closed ranges, you can use the !=
operator to check for the end condition, becuase (if your operators are defined correctly) <
implies !=
.
for (int* it = begin; it != end; ++ it) { ... }
However there’s no easy way to do this with fully-closed ranges. You’re stuck with <=
.
The only kind of iterator that supports <
and >
operations in C++ are random-access iterators. If you had to write a <=
operator for every iterator class in C++, you’d have to make all of your iterators fully comparable, and you’d fewer choices for creating less capable iterators (such as the bidirectional iterators on std::list
, or the input iterators that operate on iostreams
) if C++ used fully-closed ranges.
Eitan T
With the end()
pointing one past the end, it is easy to iterate a collection with a for loop:
for (iterator it = collection.begin(); it != collection.end(); it++)
{
DoStuff(*it);
}
With end()
pointing to the last element, a loop would be more complex:
iterator it = collection.begin();
while (!collection.empty())
{
DoStuff(*it);
if (it == collection.end())
break;
it++;
}
Andreas DM
- If a container is empty,
begin() == end()
. - C++ Programmers tend to use
!=
instead of<
(less than) in loop conditions, therefore
havingend()
pointing to a position one off-the-end is convenient.
Ich vermute, “weil das der Standard sagt”, wird es nicht schneiden, oder? 🙂
– Luchian Grigore
1. April 2012 um 9:41 Uhr
@LuchianGrigore: Natürlich nicht. Das würde unseren Respekt für die (Menschen hinter) dem Standard untergraben. Wir sollten erwarten, dass es eine gibt Grund für die vom Standard getroffenen Entscheidungen.
– Kerrek SB
1. April 2012 um 9:47 Uhr
Ich denke, diese Erklärung verdient auch Ihre Aufmerksamkeit: Eins nach dem Ende
– Schepurin
1. April 2012 um 11:04 Uhr
Kurz gesagt, Computer zählen nicht wie Menschen. Aber wenn Sie neugierig sind, warum Menschen nicht wie Computer zählen, empfehle ich Das Nichts, das ist: Eine Naturgeschichte von Null für einen eingehenden Blick auf die Schwierigkeiten, die Menschen hatten, als sie entdeckten, dass es eine Zahl gibt, die um eins kleiner als eins ist.
– John McFarlane
1. April 2012 um 19:51 Uhr
Da es nur einen Weg gibt, “Letztes” zu generieren, ist es oft nicht billig, weil es echt sein muss. Das Generieren von “Du bist vom Ende der Klippe gefallen” ist immer billig, viele mögliche Darstellungen reichen aus. (void*) “ahhhhhhh” wird gut tun.
– Hans Passant
1. April 2012 um 23:55 Uhr