ぱぴブログ

さんたろのブログ

ゲーム開発が好きな人のひとりごと

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型で返すゾ