Warum sind Standard-Iterator-Bereiche [begin, end) instead of [begin, end]?

Lesezeit: 9 Minuten

Warum sind Standard Iterator Bereiche begin end instead of begin end
Hündchen

Warum definiert der Standard end() als am Ende vorbei, statt am eigentlichen Ende?

  • 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


Warum sind Standard Iterator Bereiche begin end instead of begin end
Kerrek SB

Das beste Argument ist einfach das von Dijkstra selbst:

  • Sie möchten, dass die Größe des Bereichs ein einfacher Unterschied ist EndeStart;

  • 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 und end als ints mit Werten 0 und Nbzw. 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 Iteratorvorlage step_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

1646895009 576 Warum sind Standard Iterator Bereiche begin end instead of begin end
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 Aund 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 Position i). 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 zugreifen 1da es kein Element gibt 0 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


Warum sind Standard Iterator Bereiche begin end instead of begin end
Alok Speichern

Warum definiert der Standard end() als am Ende vorbei, statt am eigentlichen Ende?

Weil:

  1. Es vermeidet eine spezielle Handhabung für leere Bereiche. Für leere Bereiche, begin() ist gleich
    end() &
  2. Es macht das Endekriterium für Schleifen, die über die Elemente iterieren, einfach: Die Schleifen werden einfach fortgesetzt, solange end() wird nicht erreicht.

1646895010 817 Warum sind Standard Iterator Bereiche begin end instead of begin end
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?!

1646895010 378 Warum sind Standard Iterator Bereiche begin end instead of begin end
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.

1646895010 938 Warum sind Standard Iterator Bereiche begin end instead of begin end
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++;
}

1646895011 818 Warum sind Standard Iterator Bereiche begin end instead of begin end
Andreas DM

  1. If a container is empty, begin() == end().
  2. C++ Programmers tend to use != instead of < (less than) in loop conditions, therefore
    having end() pointing to a position one off-the-end is convenient.

986720cookie-checkWarum sind Standard-Iterator-Bereiche [begin, end) instead of [begin, end]?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy