[前へ]   [目次へ]   [次へ]

条件分岐「switch」(C/C++)


今回はもう一つの条件分岐「 switch 」についてです。

switch は if とは結構異なり、指定した値と等しいものを探して分岐します。
一行構文はなく、常にブロック構文を使います。
基本構文は以下のようになります。

switch(指定値){
case 条件:
   処理
case 条件:
   処理
default:
   処理

switch は「指定値」と各 case の「条件」が等しいものを検索し、一致した case の位置から実行します。
また、全ての case と一致しなかった場合は、 default の位置から実行します。
default の配置は任意であり、省略した場合で一致する case が無い場合は switch ブロックの終点から続行します。

case は1個以上の任意数を用意できますが、「条件」には定数しか使えません。
同じ switch 中に同一の条件の case は使えないので注意してください。 (最も、使えても紛らわしいだけですが) 

また、 switch case default は全てC/C++キーワードです。
また、 case の条件の後と default の後はいつもの ; (セミコロン)ではなく : (コロン)を使用する点に注意してください。

さて、例を示します。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
#include <stdio.h>

int main(void)
{
   
int n=10;
   
switch(n){//nを指定してswitchブロックを開く
   
case 5://n==5の時、ここに来る
      
puts("5");
   
default://他のcaseが全て成立しない時、ここに来る
      
puts("default");
   
case 10://n==10の時、ここに来る
      
puts("10");
   
case 15://n==15の時、ここに来る
      
puts("15");
   }
//switchブロックの終点
   
puts("分岐終了。ここは n がいくつでも通る");
   
return 0;
実行結果:
10
15
分岐終了。ここは n がいくつでも通る


まず、変数 n を10で初期化してからそれを switch の指定値として渡しています。
指定値は数値として評価できればなんでもいいので式の結果だろうが定数だろうが変数だろうが構いません。

さて、なんか妙だと思いませんか?
n は10なので puts("10") が実行されて10が表示されているのはいいのですが、
その後に何故か15も表示されています。

実は、これが switch の特徴で、 case や default はジャンプしてくる位置を指定しているだけで、
直前の case 等の終了を意味してはいないのです。
これによりさらに下にある puts("15") も実行されているのです。
これは正しくはフォールスルーと呼ぶらしいですが、私は単に貫通と呼んでいます。
フォールスルーは多分 fall through だと思われます。
通り抜けて落ちるとか、そういう意味のようです。


また、上のソースで n を8で初期化すると以下のような結果になります。
default
10
15
分岐終了。ここは n がいくつでも通る


default も case 同様貫通することが分かります。
なお、このフォールスルーを利用する場合などを除き、通常は default は一番下に配置します。


しかしこれだと条件分岐として使いにくいこと この上ないので、 break が登場します。
break はC/C++のキーワードの一つで、直近の for while do switch ブロック1つの終点に直ちに実行位置を移動します。
これを使えば switch ブロックの終点に移動できるので、そこより下の行を実行しなくなります。
ちなみに、 for while do はループのキーワードです。

<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
#include <stdio.h>

int main(void)
{
   
int n=10;
   
switch(n){//nを指定してswitchブロックを開く
   
case 5://n==5の時、ここに来る
      
puts("5");
      
break;
   
case 10://n==10の時、ここに来る
      
puts("10");
      
break;
   
case 15://n==15の時、ここに来る
      
puts("15");
      
break;
   
default://他のcaseが全て成立しない時、ここに来る
      
puts("default");
      
break;
   }
//switchブロックの終点
   
puts("分岐終了。ここは n がいくつでも通る");
   
return 0;
実行結果:
10
分岐終了。ここは n がいくつでも通る


各項目に break を追加しました。
今度は puts("10") の実行後に break が実行され、 switch ブロックの終端に実行位置が移動されています。
結果として puts("15") は実行されなくなったため、出力されていません。

これらを上手く工夫すると、バラバラの数値にある共通の処理を効率良く記述できることがあります。
バラバラの数値で共通処理というのは、しばしば遭遇します。
・・・が、結局のところ switch の出番はそう多くないのが実情です(笑)


具体的にバラバラの数値の共通処理を if と switch の2通りで書いてみます。
条件:
   nが1〜2の時、処理Aを実行する。
   nが3,5,7の時、処理Bに続いて処理Aを実行する。
   nが4,6の時、処理Cに続いて処理Dを実行する。
   nが8の時、処理Eに続いて処理Cを実行する。
   nが上記のどれでもない時、処理Dを実行する。


普通に見ても結構ややこしい印象のパターンです。
switch(n){
case 1:
   puts("処理A");
   break;
case 2:
   puts("処理A");
   break;
case 3:
   puts("処理B");
   puts("処理A");
   break;
・・・以下略
}

といったように単純に配置したくなりますが、それだと行う処理が変わった時面倒ですし、
なにより同じソースがたくさん並ぶのは面白くありません。

幸い、この条件では処理の後に別の処理を続けるパターンばかりです。
ここは、整理して各処理を1回づつの記述で済む方法がないか考えてみます。

とりあえず、各値と実行すべき順番を表にしてみます。
↓処理 →n12345678
A112-2-2--
B--1-1-1--
C---1-1-2-
D---2-2--1
E-------1-

まず、処理Aの前後関係を見てみます。
処理Aが実行される時は、処理Bが先行することがあるようです。
また、処理Aの後に実行される可能性がある処理はなく、処理C,D,Eとは全く接点がありません。
つまり、処理Bより後ろに書けば、処理Aを一回しか書かない事が可能そうです。

次に、処理Bの前後関係を見てみます。
処理Bが実行される時に先行する処理はありません。
また、後ろには処理Aが続くことがあり、処理C,D,Eとは全く接点がないことが確認できます。
このことから、処理Bは処理Aより先に書けば一回しか書かない事が可能そうです。

次に、処理Cの前後関係を見てみます。
処理Cが実行される時は、処理Eが先行することがあるようです。
また、処理Cの後には処理Dが実行される可能性があるようです。
また、処理A,Bとは全く接点がないことが確認できます。
つまり、処理Eより後で処理Dより先に書けば処理Cを一回しか書かない事が可能そうです。

次に、処理Dの前後関係を見てみます。
処理Dが実行される時は、処理Cが先行することがあるようです。
また、処理Dの後に実行される可能性がある処理はなく、処理A,Bとは全く接点がありません。
よって、処理Cより後に書けば処理Dを一回しか書かない事が可能そうです。

最後に、処理Eの前後関係を見てみます。
処理Eが実行される時に先行する処理はありません。
また、後ろには処理Cが続くことがあり、処理A,Bとは全く接点がないことが確認できます。
このことから、処理Eは処理Cより先に書けば一回しか書かない事が可能そうです。


さて、これを整理すると、処理B→処理Aのグループと処理E→処理C→処理Dのグループに分かれ、
順番がこんがらかっている訳ではないようです。

この結果から以下に switch と if の2通りで処理を起こしてみます。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
< 35>
< 36>
< 37>
< 38>
< 39>
< 40>
#include <stdio.h>

int main(void)
{
   
int n=1;
   
   
//switchの場合
   
switch(n){
   
case 3:
   
case 5:
   
case 7:
      puts(
"処理B");
      
//↓貫通します
   
case 1:
   
case 2:
      puts(
"処理A");
      
break;
   
case 8:
      puts(
"処理E");
      
//↓貫通します
   
case 4:
   
case 6:
      puts(
"処理C");
      
//↓条件付きで貫通します
      
if(n==8)break;
   
default:
      puts(
"処理D");
   }
//switchブロックの終点
   
puts("switch 分岐終了。\n");
   
   
//ifの場合
   
if((n==3)||(n==5)||(n==7))puts("処理B");
   
if(((n>=1)&&(n<=3))||(n==5)||(n==7))puts("処理A");
   
if(n==8)puts("処理E");
   
if((n==4)||(n==6)||(n==8))puts("処理C");
   
if((n<1)||(n>8)||(n==4)||(n==6))puts("処理D");
   puts(
"if 分岐終了。\n");
   
   
return 0;
実行結果:
処理A
switch 分岐終了。

処理A
if 分岐終了。


最初に switch 、続いて if で条件を処理しています。
switch の場合は18行、 if の場合は5行で、行数で見ると if の方が短く書けています。
・・・が、条件の見やすさではどうでしょうか?
switch の方は貫通と break 、そして if(n==8) の組み合わせにより n の各値の結果は
n が確定すれば比較的容易に分かると思います。( break の配置をよく見るのが重要ですが)
一方、 if の方はそれぞれの処理毎に条件が設定されていて、 n の各値の実行結果はなかなかややこしいことになっています。
プログラムを考える時は多分、 if を使ったほうが簡単でしょう。
何故なら、フォールスルーや break の位置など、 if の場合は考慮しなくてもいい項目が switch にあるからです。
しかし、後で見たときの見やすさはこの例では switch に軍配が挙がると思います。

このような、複雑な定数比較が出現した時に switch を使うことを考えます。
また、複雑でなくても同じ値に対する定数比較を多数行う時にも switch は有用です。
結局のところ switch が効果を発揮できるのは上記のような条件だけなのでこういう時しか出番がないというかなんというか(笑)

で、実際にこんな条件が出ることがあるのかというと・・・たま〜に出現することがあります(笑)
でもどっちかというとメニューの分岐等、「多数の定数比較」の方が出番が多いです。

ちなみに、今回のような場合 if よりも switch の方が比較回数が少ないので、速度的にも有利です。
最も、数千万〜数億回ループでもしないと差なんて分かりませんけどね(笑)


ネットで調べたところ、フォールスルーはどちらかといえば否定的な意見が目立つように思います。
確かにうっかり break を忘れると思わぬ形で処理が変化してしまうのでバグの混入原因になります。
他にも、 default は綴りを間違えても別解が存在するためエラーにならないという注意点があります。
綴りミスはキーワード色変えがあるエディタならすぐ気付くのでこの辺にキーワード色変えの真価があるかもしれません。

とはいえ、上記のような複雑なケースでは使う価値があると思いますし、
このような記述ができるのも switch ぐらいだと思います。
最も、この辺が switch があまり出番をもらえない理由の一つなのかもしれませんが・・・。

何はともあれ、 switch を使う時はフォールスルーをしっかりと意識しておくといいでしょう。
そうすれば、 break 忘れが原因のバグにそれほど長く悩むこともないはずです。

意図してフォールスルーを使う時のコツとしては・・・
switch を場合分けの分岐と思わないことでしょうか (分岐のキーワードなのに(笑))
一種のジャンプ命令と思って考えればフォールスルーの動きはそれほど難解なものではないはずです。


と、まぁ if と switch についていろいろ書いてみましたが、
結局のところ書く人の趣味というか、書きやすい方で書けばいいみたいな話になります。
if の方がプログラムを考える時は楽なので、1回動けばいいような使い捨てならややこしい条件でも構わないですし、
速度面でも最近のPCならどっちを使っても問題になるようなことはないでしょう。


これで四則演算版計算機を作るのに必要な要素が揃いました。
次回は、実際に「加算だけ計算機」から四則演算版計算機に改造してみます。
次回を読む前に一度自分で作ってみれば、経験値を多く獲得できるでしょう(笑)

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2008/10/17