for文を完全に理解する
この記事はMuroran Institute of Technology Advent Calendar 2018 の3日目です。
adventar.org
「ループ回数が明確ならfor文、そうでないならwhile文」
こう学んだ人も多いかもしれない。
for(i = 0; i < n; i++)
ループカウントやループ判定が一目でわかるfor文。
扱いやすいが、この丸括弧()の中がどのように実行されているかわかるだろうか。
今回はそんな、便利だけどなんとなくで書きがちなfor文について見ていこう。
1.流れをつかむ
for文の()の中には3つの文が存在することがわかる。
左から順に、初期化式、継続条件式、増減式とおこう。
for( 初期化式 ; 継続条件式 ; 増減式 ){
中身
}
これが実際にどのような流れかというと、
(初期化式) ⇒ ループ{(継続条件式) ⇒ (中身) ⇒ (増減式)} ⇒ ループ脱出
となっている。
while文に直すと一目瞭然だ
(初期化式);
while(継続条件式){
(中身);
(増減式);
}
具体例でいくと
for(i = 0; i < n; i++){ printf("HAHAHA"); }
は
i = 0; while(i < n){ printf("HAHAHA"); i++; }
と同じ流れである。なんとまあ簡単な話だ。
無論iのスコープなどには気をつけねばならぬが、
「継続条件式と増減式はどっちが先?」みたいな疑問は消えるだろう。
for(i = 0; i < n; i++)とfor(i = 0; i < n; ++i)
どっちが正しいんだーと悩む日々ともおさらばである。
2.continueの挙動
for文におけるcontinueの動きには注意しなければいけない。
(do while文におけるcontinueは以下の記事をご覧いただきたい)
papyrustaro.hatenablog.jp
(do)while文ではcontinueがきたらループの判定に飛ぶ
ということだったが、for文では少し違う。
for文ではcontinueがきたら増減式に飛ぶ
と覚えよう。
実際何気に、continue時にiの値をインクリメントしていたかもしれないが...
最初の話の考えでfor文をwhile文にするとわかりやすい。
for文
for(i = 0; i < n; i++){ if(i == 3) continue; printf("Hello\n"); }
while文にしてみる(?)
i = 0; while(i < n){ if(i == 3) continue; printf("Hello\n"); i++; }
while文では無限ループに陥っている。
しかしfor文では問題ない。
for文におけるcontinueはwhile文とは少し動きが違うのである。
<for文におけるcontinue>
i = 0;
while(i < n){ ×②ここではなく
if(i == 3) continue; ①continueがきたら
i++; ○②ここに飛ぶ
}
3.()の中身
文の省略
何気なく書いている()の中身だが、省略してもいいことをご存知の方も多いだろう。
for( A ; B ; C ){
文;
}
を
A;
while(B){
文;
C;
}
と考え、省略する部分がなくなるだけである。
ただしcontinueのほかに注意すべき点がある。
それは継続条件式(B)を省略したときtrueになるということである。
どういうことかというと
for( ; ; ){
文;
}
は
while(1){ 文; }
になるということである(C言語にtrueがないため1とおいた)。
文はなんでもいい?
初期化式、継続条件式、増減式という名前をつけたが、
必ず増減式で値を変える必要はない。
くどいようだが
(初期化式);
while(継続条件式){
(中身);
(増減式);
}
なだけであり、以下のような書き方もできる(無論実用性はないが)
for(printf("Hello"); i++ < 2; printf("World\n")){ printf("ふざけたコードだ"); }
while文にすればなんてことはない
printf("Hello"); while(i++ < 2){ printf("ふざけたコードだ"); printf("World\n"); }
4.コンマ演算子を使った複数の式
コンマ演算子とは
カンマ派もいるかもしれないが、コンマ演算子の軽い説明。
A, B;
という文があったときAを評価、結果を破棄⇒Bを評価、Bの結果を返すというものである。
簡単な例をだすと
int a, b, c, x; a = 1; b = 2; c = 3; x = 0; while((a, b, c, x) == 0){ printf("パピルス"); } while(a == 0, b == 0, c == 0, x == 0){ printf("サンズ"); }
この2つのwhile文はどちらも無限ループに陥る。
コンマ演算子において一番右の文以外は評価(実行)された後破棄されるのである。
for文に使ってみよう
このコンマ演算子を用いると初期化式、継続条件式、増減式において
複数の式を書くことができる。
#include <stdio.h> int i, j; for(i = 0, j = 5; i < 2, j < 10; i++, j++){ printf("i = %d\t j = %d\n", i, j); }
実行結果は以下のとおりである。
i = 0 j = 5 i = 1 j = 6 i = 2 j = 7 i = 3 j = 8 i = 4 j = 9
i < 2の評価が無視され、j < 10の評価がされていることに注目してほしい。
例のごとくwhile文にしてみる
#include <stdio.h> int i, j; i = 0, j = 0; while(i < 2, j < 10){ printf("i = %d\t j = %d\n", i, j); i++, j++; }
今回の復習
私のTwitterにもあげた問題だが、次のコードの実行結果を予想してほしい。
実際に実行して確かめてみよう。
#include <stdio.h> int main(void){ int i = 0; for(printf("A"); printf("B"), i++ < 2; printf("C")){ if(i == 1) continue; printf("D"); } return 0; }
余談 printf関数の返り値
for文についてはここまでで終わりだ。
先ほどの復習問題の継続条件式
printf("B"), i++ < 2
を
i++ < 2 , printf("B")
としたとき無限ループに陥る。
なぜか。下の方の式が何をしているのか考えてみよう。
まずコンマ演算子によりi++ < 2が評価され、結果が破棄される。
そしてprintf("B")の評価である。
while(printf("B")) である
実はprintf()は出力した文字数のバイト数をint型で返している。
故にprintf("B")は1を返している。
while(1)...あっ(察し)
みなさんもループの判定の式でprintf関数を使うときは気を付けよう(誰も使わん)
ちなみにscanf関数は入力に成功した項目の数をint型で返すゾ