Cで逆順ループを書くなら、この一行に尽きる
Iterating Backwards in C
size_tのカウンタで逆順に回そうとしてi >= 0と書くと、条件は常に真になり、countが0ならiは即座にSIZE_MAXへ。符号付きのssize_tに逃げても変換規則やWindows対応、32ビットでの切り詰めなど問題が尽きない。正解はfor(size_t i = count; i--; )という形で、後置デクリメントが境界内の値を返しつつ0でループを終える。
この書き方なら符号付きのループ変数に変換する手間もなく、ループがどれだけ簡潔か分かるだろう!
HNでの議論
32- kenferry
「やりすぎ」なのは分かっているけど、誰にも見られないコードを書くならこれだね、
while (i --> 0) {
// array[i] を使う
}
読みやすくて(理解できるかは別として)、正しい!
- bugake
知らない人のために説明すると、forループで逆順に数えるのが速いのは、符号なし整数を0と比較する方が、他のどんな値と比較するよりも一般的に速いからだ。
また、`for (i >= 0)` を使ってはいけないとも書いてある。符号なし整数は常に0以上なので、ループが永遠に終わらないからだ。
- Joker_vD
それがイノベーションの代償ってやつだろうね。Cが「for」ループを発明する前は、他の言語の同等のものはだいたいこんな感じだった:
FOR var = start [DOWN] TO end BY step DO ... END
独自のインクリメント式はなく、「step」もたいてい定数しか許されなかった(その場合、言語にはたいていDOWN指定子がなく、代わりにstepの符号が使われた)。そこへDennis RitchieがCを発明し、従来の「do」ループ(FortranがDOでこの構文を始めたのでDOループと呼ばれた。Algolは代わりにFORを使ったが、ほとんどの人はFortranの命名を好んだ……Cが支配的になるまでは。とはいえそれはさらに20年後のことだが)を捨てて、自分の発明を採用した。彼はリンクリストや文字列を簡単に反復できることの方がずっと喫緊の課題だと感じていたからだ:
for(p=head; p; p=p->next) { ... }
for(s=str; *s; s++) { ... }
もちろん欠点は、もうこう素直には書けなくなったことだ:
for size_t i = count - 1 downto 0 do
...
end
- kccqzy
だからこそ私はC++とその逆順イテレータを使うのが好きなんだ。rbeginとrendアクセサは最初からコンテナに入っている。インデックスを使うCの版よりずっとエラーが起きにくい。(もちろんインデックスを使わざるを得ない状況もあるから、i-->0のトリックも使うけど、それはずっと稀だ。)
- randomblock1
ほとんどの人は明示的な版を使っていると思っていた:
for (size_t i = count; i > 0; --i) {
// array[i - 1] を使う
}
こういうこともできる:
size_t index = i - 1;
i-1を使わなくて済むようにね。コンパイラは結局これを最適化して同一にするはずだ。