なぜ標準はend()
を実際の終わりではなく、終わりを過ぎたものとして定義するのですか?
最も簡単な引数は、 Dijkstra自身 によって作成されたものです。
範囲のサイズを単純な差にしたいend−begin;
シーケンスを空のシーケンスに縮退させる場合、下限を含めることはより自然です。また、代替(exclude下限)の存在が必要になるためです。 「開始前の」センチネル値。
それでも、1ではなく0でカウントを開始する理由を正当化する必要がありますが、それは質問の一部ではありませんでした。
[begin、end)規則の背後にある知恵は、自然に連鎖する範囲ベースの構造への複数のネストされた呼び出しまたは反復された呼び出しを処理する任意の種類のアルゴリズムがある場合、何度も報われます。対照的に、二重に閉じた範囲を使用すると、1つずつずれ、非常に不快でノイズの多いコードが発生します。たとえば、パーティション[nを考えます、n1)[n1、n2)[n2、n3)。別の例は、_end - begin
_回実行される標準の反復ループfor (it = begin; it != end; ++it)
です。両端が含まれている場合、対応するコードははるかに読みにくくなります。空の範囲をどのように処理するかを想像してください。
最後に、なぜカウントをゼロから開始するのかという素敵な引数を作成することもできます:範囲を指定した場合[〜#〜] n [〜#〜]要素(たとえば、配列のメンバーを列挙する場合)、0は自然な「始まり」なので、範囲を[0、[〜#〜] n [〜#〜])、厄介なオフセットや修正なし。
簡単に言うと、範囲ベースのアルゴリズムのどこにも番号_1
_が表示されないという事実は、[begin、end)規則の直接的な結果であり、その動機付けです。
実際、イテレータがシーケンスの要素をatを指していないがbetween、次の要素へのアクセスを間接参照します。次に、「ワンエンドエンド」イテレータが突然意味をなします。
+---+---+---+---+
| 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番目のシーケンスの最初の要素が与えられます。
逆イテレータの「off-by-one」でさえ、そのように突然明らかになります。その順序を逆にすると、次のようになります。
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
対応する非逆(ベース)イテレータを以下の括弧内に記述しました。ご覧のとおり、i
(私はri
と名付けました)stillに属する逆反復子は、要素B
とC
の間にあります。ただし、シーケンスが逆になっているため、要素B
はその右側にあります。
なぜ標準はend()
を実際の終わりではなく、終わりを過ぎたものとして定義するのですか?
なぜなら:
begin()
はend()
と等しくなります&end()
に到達しない限り単純に継続します。なぜなら
size() == end() - begin() // For iterators for whom subtraction is valid
する必要はありませんawkward
// 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()
は何を返しますか?
reallyが必要ですかanother無効なイテレータを返すinvalid()
と呼ばれるメンバー?!
2人のイテレーターは既に十分に苦痛です...
ああ、this 関連する投稿を参照してください。
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)
を呼び出す方がはるかに簡単です。
さらに、半閉じた範囲で作業する場合、(演算子が正しく定義されている場合)_!=
_は_<
_を意味するため、_!=
_演算子を使用して終了条件を確認できます。
_for (int* it = begin; it != end; ++ it) { ... }
_
ただし、完全に閉じた範囲でこれを行う簡単な方法はありません。あなたは_<=
_で立ち往生しています。
C++で_<
_および_>
_操作をサポートする唯一の種類の反復子は、ランダムアクセス反復子です。 C++のすべてのイテレータクラスに対して_<=
_演算子を記述する必要がある場合、すべてのイテレータを完全に比較可能にする必要があり、機能の低いイテレータ(双方向イテレータなど)を作成するための選択肢が少なくなります_std::list
_、またはC++が完全に閉じた範囲を使用した場合は、iostreams
で動作する入力反復子。
end()
が末尾を過ぎているので、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++;
}
_
begin() == end()
。!=
_(より小さい)の代わりに_<
_を使用する傾向があるため、end()
が1つ後ろの位置を指していると便利です。