なぜ規格end()は実際の終わりではなく、終わりを過ぎたものとして定義するのですか?
なぜ規格end()は実際の終わりではなく、終わりを過ぎたものとして定義するのですか?
回答:
最良の議論は簡単にダイクストラ自身が作ったものです:
範囲のサイズを単純な差にしたい場合end - begin ;
シーケンスを空のシーケンスに退化する場合、下限を含めることはより「自然」です。また、代替(下限を除く)には「1つ前の」センチネル値の存在が必要になるためです。
なぜ1ではなく0からカウントを開始するのかを正当化する必要がありますが、それは質問の一部ではありませんでした。
[begin、end)規則の背後にある知恵は、自然に連鎖する範囲ベースの構造に対する複数のネストされた呼び出しまたは反復された呼び出しを処理するアルゴリズムの種類がある場合に、何度も報われます。対照的に、2重に閉じた範囲を使用すると、1つずつずれ、非常に不快でノイズの多いコードが発生します。たとえば、パーティション[ n 0、n 1)[ n 1、n 2)[ n 2、n 3)をます。別の例は、標準的な反復ループfor (it = begin; it != end; ++it)であり、これはend - begin時間を実行します。両端が含まれている場合、対応するコードは読みにくくなります。空の範囲を処理する方法を想像してみてください。
最後に、カウントをゼロから開始する必要がある理由を説明することもできます。N要素の範囲が指定されている場合(たとえば、配列のメンバーを列挙する場合)、先ほど確立した範囲のハーフオープン規則を使用すると、 0は自然な「始まり」であるため、範囲を[0、N)と書くことができ、厄介なオフセットや修正はありません。
簡単に言うと1、範囲ベースのアルゴリズムのどこにも数字が表示されないという事実は、[開始、終了)規約の直接の結果であり、その動機です。
beginとendとしてint値を持つの0とN、それぞれ、それが完全に適合しています。おそらく、これ!=は従来の<に比べてより自然な状態ですが、より一般的なコレクションについて考え始めるまで、そのことを発見することはできませんでした。
++-incrementableイテレータテンプレートを作成する必要があります。このテンプレートstep_by<3>には、最初にアドバタイズされたセマンティクスが含まれます。
!=する必要があるときに使用する場合<、それはバグです。ちなみに、このエラーの王は、単体テストやアサーションで簡単に見つけることができます。
実際、イテレータがシーケンスの要素を指しているのではなくの間にあり、次の要素へのアクセスを逆参照することを考えると、イテレータに関連する多くの要素が突然、はるかに意味をなすようになります。次に、「過去の終わり」のイテレータが突然意味をなすようになります。
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
明らかにbeginシーケンスの始まりをend指し、同じシーケンスの終わりを指します。逆参照beginは要素Aにアクセスしますが、逆参照endはそれに適切な要素がないため意味がありません。また、i途中にイテレータを追加すると、
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^ ^
| | |
begin i end
あなたはすぐにから要素の範囲ことを見るbeginにはi要素が含まれているAとBの要素の範囲ながらiにend要素が含まれていますCとD。逆参照iすると、要素の権利が与えられます。つまり、2番目のシーケンスの最初の要素です。
逆反復子の「オフバイワン」でさえ、突然そのように明らかになります。その順序を逆にすると、次のようになります。
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
以下の括弧内に、対応する非リバース(ベース)イテレーターを書きました。ご覧のとおり、i(私がと命名したri)に属する逆反復子は、まだ要素Bとの間にありCます。ただし、順序を逆にしたため、要素Bはその右側にあります。
foo[i]すぐにアイテムの省略形です)の後の位置i)。考えてみると、「位置iの直後の項目」と「位置iの直前の項目」に別々の演算子を使用すると、多くのアルゴリズムが隣接する項目のペアを処理し、「ポジションiのいずれかの側のアイテムは、「ポジションiおよびi + 1のアイテム」よりもクリーンな場合があります。
begin[0](ランダムアクセスイテレータを想定して)指定された数値でelement 1にアクセスします0。
start()、特定のプロセスを開始するためにクラスで関数を定義する必要がある場合など、既存のプロセスと競合すると煩わしくなります)。
それで
size() == end() - begin() // For iterators for whom subtraction is valid
あなたは厄介なことをする必要はありません
// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }
あなたは誤って次のような誤ったコードを書くことはありません
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
また、有効な要素をポイントすると何がfind()返されend()ますか?
あなたがでください本当にしたい他のメンバーと呼ばれるinvalid()無効なイテレータを返します!
2つのイテレータはすでに十分に苦痛です...
ああ、そしてこの関連記事を見てください。
endが最後の要素の前にあった場合、どのようにinsert()して真の終わりを迎えますか?!
半分閉じた範囲のイテレータイディオム[begin(), end())は、元はプレーン配列のポインター演算に基づいています。その操作モードでは、配列とサイズが渡された関数があります。
void func(int* array, size_t size)
[begin, end)その情報があれば、半分閉じた範囲への変換は非常に簡単です。
int* begin;
int* end = array + size;
for (int* it = begin; it < end; ++it) { ... }
完全に閉じた範囲で作業するには、より困難です。
int* begin;
int* end = array + size - 1;
for (int* it = begin; it <= end; ++it) { ... }
配列へのポインターはC ++の反復子であるため(そしてこれを可能にするように構文が設計されています)、を呼び出すstd::find(array, array + size, some_value)よりも呼び出す方がはるかに簡単std::find(array, array + size - 1, some_value)です。
プラス、あなたが半分閉じた範囲で動作している場合、あなたが使用することができます!=(あなたの事業者が正しく定義されている場合)becuase、終了条件をチェックする演算子を<意味します!=。
for (int* it = begin; it != end; ++ it) { ... }
ただし、完全に閉じた範囲でこれを行う簡単な方法はありません。あなたは立ち往生してい<=ます。
C ++ でサポート<および>操作される唯一のイテレータは、ランダムアクセスイテレータです。<=C ++ですべてのイテレータークラスの演算子を作成する必要がある場合、すべてのイテレーターを完全に比較可能にする必要があり、能力の低いイテレーター(の双方向イテレーターstd::listや入力イテレーターなど)を作成するための選択肢が少なくなります。iostreamsC ++が完全に閉じた範囲を使用した場合、)で動作します。
ではend()最後過去ポインティング1は、forループでコレクションを反復処理するのは簡単です:
for (iterator it = collection.begin(); it != collection.end(); it++)
{
DoStuff(*it);
}
end()最後の要素を指して、ループはより複雑になります:
iterator it = collection.begin();
while (!collection.empty())
{
DoStuff(*it);
if (it == collection.end())
break;
it++;
}