読者です 読者をやめる 読者になる 読者になる

カンマ区切りでwhile,for,if文などの条件式を書く時の注意点

プログラミング

自分はこれで苦労したことがあったので備忘録としても書いておくことにする

CやC++ではif(0 < n)などのように括弧内に条件式を書く.
これはwhile(0 < n) for(;i < n;) のようにwhile for文にも同じように書ける.

AOJ等でこんな問題が出る.
カードゲーム | Aizu Online Judge

標準入出力を行うプログラムを作成して下さい。
上記の形式で複数のデータセットが与えられます。nが0のとき入力が終了します。

nの入力の部分だけなら

while (1){
  scanf("%d", &n);
  if (n == 0){
    break;
  }
}

このように書けるだろう.しかしこういう風にも書ける.

while (scanf("%d", &n), n != 0)

後者のほうがコンパクトで見やすい.
因みに,while (scanf("%d", &n), n)と書いても同じような結果になる.
これは,(カンマ)以降に書かれているものを条件式としている.

ただし,このような書き方で注意すべき点がある.
ビデオテープ | Aizu Online Judge

複数のデータセットが与えられます。各データセットは以下のとおりです。
時(整数)分(整数)秒(整数)
入力は、3つの -1 で終わります。

分かった!!さっきの書き方でやると...

while (scanf("%d%d%d", &h, &m, &s), h != -1, m != -1, s != -1)

これでは間違い.この書き方ではs != -1しか条件式として扱ってくれない.
この問題では3つの終了する条件があるので,論理演算子 を使う必要がある.

h-1のときとm-1のときとs-1のときだから...

while (scanf("%d%d%d", &h, &m, &s), h != -1 && m != -1 && s != -1)

一見正解に見えるが,実はこれも間違い.ここで改めて論理演算子の意味を思い出してほしい.
以下はa && b,a || bのときを表している.

&&
a b 真偽
0 0 0(偽)
1 0 0(偽)
0 1 0(偽)
1 1 1(真)
||
a b 真偽
0 0 0(偽)
1 0 1(真)
0 1 1(真)
1 1 1(真)

例えば先程のコードで-1 2 2と入力したとする.
条件は 3つの-1で終わる ことだ.しかしこれではwhileを抜けてしまい終了してしまう.
詳しく見ていこう.

h には-1m には2s には2 が入る.
まずh != -1これは0(偽)になる.m != -1 これは1(真).s != -1も1(真)だ.
論理演算子&&なので表に合わせてみると0 && 1 && 1になり最終的に0になる.
0ということはwhile文を抜けることになる.3つとも-1ではない のにだ.

なので正解は

while (scanf("%d%d%d", &h, &m, &s), h != -1 || m != -1 || s != -1)

これなら0 || 1 || 11になり条件に合わずに抜けることが無くなる.
考えてみれば当然だが,,(カンマ)で区切らない方法で書くと

while (1){
  scanf("%d%d%d", &h, &m, &s);
  if (h != -1 && m != -1 && s != -1){ // ||じゃなくて&&
    break;
  }
}

となり&&で書くのでカンマ区切りと逆になってしまう.
僕のようなプログラミング初心者が躓いてしまい,AOJでWAを出される羽目になってしまうので注意していただきたい. (因みにこの問題ではチェックが甘いのでh != -1だけでも通る)

何か間違っている点があればコメント等で教えていただけると幸いです.