PHPで(知らずに)以下のようなコードを書いていてはまった。
<?php $sum = 0; foreach (range(1, 5) as $i) { $val = $i; printf("%3d\n", $val); switch ($val) { case 1: printf("%5d: %s\n", $val, "One"); break; case 2: printf("%5d: %s\n", $val, "Two"); break; case 3: printf("%5d: %s\n", $val, "Three"); continue; case 4: printf("%5d: %s\n", $val, "Four"); case 5: printf("%5d: %s\n", $val, "Five"); break; } $sum += $val; } printf("sum=%d\n", $sum);
実行すると以下のような結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=15
‥あれ、「$val == 3」のときはスキップされるから、「sum=12」になるはず‥
ためしに色んな言語で(時には無理矢理(謎))同じことをしてみる。
Javaの場合
public class Hoge { public static void main(String[] args) { int sum = 0; for (int i = 0; i < 5; ++i) { int val = i + 1; System.out.printf("%3d%n", val); switch (val) { case 1: System.out.printf("%5d: %s%n", val, "One"); break; case 2: System.out.printf("%5d: %s%n", val, "Two"); break; case 3: System.out.printf("%5d: %s%n", val, "Three"); continue; case 4: System.out.printf("%5d: %s%n", val, "Four"); case 5: System.out.printf("%5d: %s%n", val, "Five"); break; } sum += (val); } System.out.printf("sum=%d%n", sum); } }
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」になる。
C言語の場合
#include <stdio.h> int main(int argc, char* argv[]) { int i; int sum = 0; int val; for (i = 0; i < 5; ++i) { val = i + 1; printf("%3d\n", val); switch (val) { case 1: printf("%5d: %s\n", val, "One"); break; case 2: printf("%5d: %s\n", val, "Two"); break; case 3: printf("%5d: %s\n", val, "Three"); continue; case 4: printf("%5d: %s\n", val, "Four"); case 5: printf("%5d: %s\n", val, "Five"); break; } sum += val; } printf("sum=%d\n", sum); }
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。
bash
#! /bin/bash sum=0 for ((i = 0; i < 5; ++i)); do val=$((i + 1)) printf "%3d\n" $val case $val in 1) printf "%5d: %s\n" $val "One" ;; 2) printf "%5d: %s\n" $val "Two" ;; 3) printf "%5d: %s\n" $val "Three" continue; ;; 4|5) case $val in 4) printf "%5d: %s\n" $val "Four" ;; esac printf "%5d: %s\n" $val "Five" ;; esac sum=$((sum + val)) done printf "sum=%d\n" $sum
break文に当たる「;;」を省略できないので、fall throughの部分は無理矢理だが。
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」
Perl
Perlは書けないので、以下の辺りを参考に。
use Switch; my $i; my $sum = 0; for ($i = 0; $i < 5; ++$i) { my $val = $i + 1; printf("%3d\n", $val); switch ($val) { case 1 { printf("%5d: %s\n", $val, "One"); } case 2 { printf("%5d: %s\n", $val, "Two"); } case 3 { printf("%5d: %s\n", $val, "Three"); next; } case [4, 5] { switch ($val) { case 4 { printf("%5d: %s\n", $val, "Four"); } } printf("%5d: %s\n", $val, "Five"); } } $sum += $val; } printf("sum=%d\n", $sum);
同じくfall throughの部分は無理矢理。
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=15
‥あれ、「sum=15」だ‥
下記で追調査。
Ruby
Rubyも書けないので、以下の辺りを参考に。
ちなみに、脱線するが、この辺りが面白かった。
sum=0 for i in 1..5 do val = i; printf("%3d\n", val); case val when 1 printf("%5d: %s\n", val, "One"); when 2 printf("%5d: %s\n", val, "Two"); when 3 printf("%5d: %s\n", val, "Three"); next; when 4, 5 case val when 4 printf("%5d: %s\n", val, "Four"); end printf("%5d: %s\n", val, "Five"); end sum += val; end printf("sum=%d\n", sum);
同じくfall throughの部分は無理矢理。
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。
一旦まとめると‥
言語 | switch(case)文の中でのループスキップ |
---|---|
PHP | ×(※1) |
Java | ○ |
C言語 | ○ |
bash | ○ |
Perl | ×(※2) |
Ruby | ○ |
と、こんな感じになった。
‥って書いちゃうと誤解を招くので、さっさと追調査。
(※1)PHPの場合の仕様
実は、マニュアルの中にこんなことが書いてあった。
注意: 他の言語とは違って、 continue命令は switchにも適用され、breakと同じ動作をします。 ループの内部でswitchを使用しており、 外側のループの処理を続行させたい場合には、continue 2 を使用してください。
‥おぉ!
ということで、以下のように書き直してみる。
<?php $sum = 0; foreach (range(1, 5) as $i) { $val = $i; printf("%3d\n", $val); switch ($val) { case 1: printf("%5d: %s\n", $val, "One"); break; case 2: printf("%5d: %s\n", $val, "Two"); break; case 3: printf("%5d: %s\n", $val, "Three"); continue 2; case 4: printf("%5d: %s\n", $val, "Four"); case 5: printf("%5d: %s\n", $val, "Five"); break; } $sum += $val; } printf("sum=%d\n", $sum);
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。
この構文、もちろん単なる多重ループのときにも有効で、変なラベル(何)をつける必要がないというメリットがある。一方で、ぱっと見ではどこに飛ぶのかが分からず、ループの数を数え間違えるとひどい事になるというデメリットがある。
(※2)Perlの場合の仕様
(PHPと同じように書いてみたときのエラーメッセージからたどり着いたのは内緒(謎))
Perlでは、ラベルによって、どのループに対する「next」なのかを指定できる。
そもそも、PerlのSwitchモジュールにおける「next」はfall throughのためのものらしい。
つまり、先のPerlプログラム中の「case 3」よりも下に、$valが3のときにマッチするcase句があったら違う結果を引き起こしていたというわけだ。怖い怖い‥
というわけで、上記を踏まえて書き直したのが以下。
use Switch; my $i; my $sum = 0; loop: for ($i = 0; $i < 5; ++$i) { my $val = $i + 1; printf("%3d\n", $val); switch ($val) { case 1 { printf("%5d: %s\n", $val, "One"); } case 2 { printf("%5d: %s\n", $val, "Two"); } case 3 { printf("%5d: %s\n", $val, "Three"); next loop; } case 4 { printf("%5d: %s\n", $val, "Four"); next; } case [4, 5] { printf("%5d: %s\n", $val, "Five"); } } $sum += $val; } printf("sum=%d\n", $sum);
Switchモジュールのfall throughの仕様をちゃんと使ってみた。
また、この「ラベル」の概念はJavaでも同じですね。(もっと言えば、悪しきモノとして封印されているC言語のgotoとか(略))
で、実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。