foreach
ループ
Dで最もよく使われる文の一つは、foreach
ループだ。これは、コンテナ(または範囲)のすべての要素に同じ操作を適用するために使う。
コンテナの要素に操作を適用することは、プログラミングでは非常に一般的である。for
ループの章では、for
ループで、各反復で加算されるインデックス値によって配列の要素にアクセスすることを学んだ。
すべての要素を反復処理する手順は次の通りだ:
- カウンターとして変数を定義し、通常は
i
- 配列の
.length
プロパティの値までループを反復する - 加算する
i
- 要素にアクセスする
foreach
は本質的に同じ動作だが、これらのステップを自動的に処理することでコードを簡素化する:
foreach
の威力の部分は、コンテナの型に関係なく同じように使用できることにある。前の章で見たように、for
ループで連想配列の値を反復処理する1つの方法は、まず配列の.values
プロパティを呼び出すことだ。
foreach
連想配列に対して特別な処理は必要ない。配列と同じように使用できる:
foreach
の構文
foreach
は3つのセクションから成る:
- container_or_rangeは要素が格納されている場所を指定する。
- operationsは、各要素に適用する操作を指定する。
- namesは、要素の名前、およびコンテナや範囲の型に応じてその他の変数を指定する。名前の選択はプログラマーに任されているが、これらの名前の数および型は、コンテナの型によって決まる。
continue
break
これらのキーワードは、for
ループでの意味と同じだ:continue
は、現在の要素の残りの操作を完了する前に次の反復に移動し、break
はループを完全に終了する。
foreach
配列の場合
foreach
を単純な配列で使用し、namesセクションに1つの名前が指定されている場合、その名前は各反復の要素の値を表す。
名前セクションに2つの名前が指定されている場合、それらはそれぞれ自動カウンタと要素の値を表す。
カウンターはforeach
によって自動的に加算される。任意の名前を付けることができるが、i
は自動カウンターとして非常に一般的な名前だ。
foreach
文字列とstd.range.stride
文字列は文字の配列であるため、foreach
は文字列に対して配列と同じように動作する: 1つの名前は文字を指し、2つの名前はカウンターと文字をそれぞれ指す:
ただし、UTFコード単位であるため、char
とwchar
はUnicodeコードポイントではなく、UTFコード単位を反復処理する:
ç を構成する2つのUTF-8コード単位は、別々の要素としてアクセスされる:
0 | a |
1 | b |
2 | c |
3 | |
4 | � |
5 | d |
foreach
ループで文字列のUnicode文字を反復処理する1つの方法は、std.range
モジュールにあるstride
を使うことだ。stride
は、文字列をUnicode文字で構成されるコンテナとして表現する。2番目のパラメータは、文字をストライドするステップ数だ。
文字列の文字型に関係なく、stride
は常にその要素をUnicode文字として表現する。
a
b
c
ç
d
このループに自動カウンタを含めることができなかった理由については、以下で説明する。
foreach
連想配列
連想配列でforeach
を使用する場合、1つの名前は値を指し、2つの名前はそれぞれキーと値を指す。
連想配列は、キーと値を範囲として提供することもできる。範囲については、後の章で説明する。.byKey
、.byValue
、および.byKeyValue
は、foreach
ループ以外のコンテキストでも役立つ、効率的な範囲オブジェクトを返す。
.byValue
は、上記の通常の値反復よりも、foreach
ループでは何の利点もない。一方、.byKey
は、連想配列のキーだけを反復する唯一の効率的な方法である。
.byKeyValue
は、タプルに似た変数を通じて各キーと値の要素を提供する。キーと値は、その変数の.key
および.value
プロパティを通じて個別にアクセスできる。
foreach
数値範囲
数値範囲は、スライスとその他の配列機能の章で既に説明した。数値範囲は、container_or_rangeセクションで指定できる:
10は範囲に含まれるが、15は含まれないことに注意。
foreach
構造体、クラス、および範囲
foreach
は、foreach
ループで独自の反復を定義するユーザー定義型のオブジェクトにも使用できる。構造体およびクラスは、opApply()
メンバー関数、または一連のrangeメンバー関数によって、foreach
反復をサポートしている。これらの機能については、後の章で説明する。
カウンターは配列の場合のみ自動である
自動カウンターは、配列を反復処理する場合にのみ提供される。他のコンテナには2つのオプションがある
- 後で構造体とクラスによる
foreach
の章で説明する、std.range.enumerate
を利用すること。 - カウンター変数を明示的に定義して加算する:
このような変数は、特定の条件もカウントする場合にも必要である。例えば、次のコードは、10で割り切れる値のみをカウントする。
出力:
: 1
1: 0
: 15
2: 10
: 3
: 5
3: 20
4: 30
要素のコピーではなく、要素そのもの
foreach
ループは通常、コンテナに格納されている実際の要素ではなく、要素のコピーを提供する。これはバグの原因となる可能性がある。
この例を見るために、配列の要素の値を2倍にする次のプログラムを見てみよう。
プログラムの出力は、foreach
の本体内で各要素に代入された値が、コンテナ内の要素に影響を与えないことを示している:
前 | [1.2, 3.4, 5.6] |
---|---|
後 | [1.2, 3.4, 5.6] |
これは、number
が配列の実際の要素ではなく、各要素のコピーだからだ。実際の要素を操作するには、ref
キーワードを使用して、実際の要素への参照として名前を定義する必要がある:
新しい出力では、代入が配列の実際の要素を変更することがわかる:
前 | [1.2, 3.4, 5.6] |
---|---|
後 | [2.4, 6.8, 11.2] |
ref
キーワードは、各反復でnumber
を実際の要素の別名にする。その結果、number
を通じた変更は、コンテナの実際の要素を変更する。
コンテナの整合性は保持されなければならない
ref
変数を使用してコンテナの要素を変更することは問題ないが、コンテナの反復実行中にコンテナの構造を変更してはならない。例えば、foreach
ループの実行中に、コンテナから要素を削除したり、要素を追加したりしてはならない。
このような変更は、ループの反復処理の内部動作を混乱させ、不正なプログラム状態を引き起こす可能性がある。
foreach_reverse
逆方向に反復する
foreach_reverse
foreach
と同じように機能するが、逆方向に反復する:
出力:
3
2
1
foreach_reverse
は、range関数retro()
と同じ目的を果たすため、あまり使用されない。retro()
については、後の章で説明する。
演習
連想配列は、キーから値へのマッピングを提供することを知っている。このマッピングは単方向で、値はキーによってアクセスできるが、その逆は不可能だ。
次のような連想配列が既に存在すると仮定する:
この連想配列とforeach
ループを使用して、values
という名前の別の連想配列を埋めてみよう。新しい連想配列は、名前に対応する値を提供する必要がある。例えば、次の行は20を出力する必要がある。