見出し画像

多分岐処理 - 計算型GOTOとswitch

プログラムの中で、条件によって処理を変える条件分岐(if文)はどの処理系にもある一般的な処理ですが、条件がたくさんあって、そのひとつひとつの条件に当てはまるかを調べる必要がある時に便利な構文が用意されていることがあります。


古のFORTRANの場合から行きましょう。

FORTRANには2つの特別なGOTO文があります。ひとつが計算型GOTO文で、

GO TO (10,20,30) I

などと書きます。最後の整数変数iの値が1であれば()内の最初の行番号である10、2であれば次の行番号である20という関係で、該当する行番号に飛びます。iの値が()に書かれている範囲に無いときには、このGOTO文は何もせず、次の行から実行を続けます(0以下の場合も同じ)。

もうひとつが割当型GOTO文で、

ASSIGN 10 to I
GO TO I

とすることで、行番号10に飛びます。飛び先である行番号がASSIGN文によって変数に格納されているわけです(この形式では GO TO I (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) と書くことも出来て、計算型GOTO文にそっくりな形式もあるので注意を)。

fortran Language Reference - 194ページにGOTO関係があります

https://www.cc.nagasaki-u.ac.jp/sec_online_manual/f90/Fortran_Language_Reference.pdf

ただ、これらのGOTO文は、レガシーな機能とされていて廃止される予定にはなっているようです。


次はBASICです。

計算型はFORTRANとそっくりで

ON A GOTO 10,20,30

で変数Aの値が1,2,3の時に、それぞれ行番号10,20,30に飛びます。ほぼ同じ用途で

ON A GOSUB 10,20,30

とサブルーチンを呼ぶことも出来ます。Aの値に対応する行番号が無いときには、FORTRANと同様、次の行から実行を続けます。

12.よく使うBASICの命令


さて、そろそろC言語に行きますか。

C言語には行番号が無いので、飛び先の指定はラベルになります。

switch ( a ) {
case 1:
  break;
case 2:
  break;
case 3:
  break;
default:
}

caseの部分に書くことが出来るのは定数で変数はいけません。このcaseの部分がラベルとして扱われます。switchに書く変数は式で書いても良く、数値ではなくて文字であってもcaseに書かれた定数と比較して一致するものであれば構いません。

なおC言語のswitch文は合致するcaseへ飛んだ後に、次のcaseをスキップすることはありません。明示的にbreakを書かないと、case 1に飛んだ後に case 2 からを実行します。case はあくまでラベルです。これが便利なこともあるにはありますが、ほとんどの「ケース」では、やらかす人のほうが多数派です。

そして合致するラベルがない場合、defaultを書けば、そこに飛びますし、defaultがなければFORTRANなどと同じくswitchの{}の次から実行が続きます。

【C言語入門】switch-case文の使い方(数値、文字列で複数条件分岐)

※switchの{}の意味には微妙なところがあって、breakはスコープを抜けるだけですが、continueは、このスコープの外にあるfor(など)に働きます。またローカルな変数を宣言できるかみたいなややこしい話もありますが、それはC言語の話としてまたの機会に。


他のJavaであったり、Pythonだったり、モダンなFORTRANやBASICにも類似の構文はありますが、switchの部分に文字列が書けたり、caseに変数が使えたり、breakを書かなくても他のcaseの部分はスキップしてくれたり、微妙な差があるので、気をつけないとハマるところです。


さて、どうしてこのような構文があるのかというと、ひとつひとつifで条件を判定することにはいくつかの問題があるからです。

C言語で例を書きます。

if ( a == 0 ) { printf(“0\n”); }
else {
  if ( a == 1 ) { printf(“1\n”); }
}

とするか

if ( a == 0 ) printf(“0\n”);
if ( a == 1 ) printf(“1\n”);

でも a が同時に0でも1でもあることは無いので、どちらでも同じといえば同じです。ただ何回もaと値を比較することにはなりますし、入れ子にしたほうが判定が成立した後にはもう判定をすることが無いので良さそうですが、入れ子が深くなってどこかで{}の数を間違えることも良くあります。ここは定数と比較しているから良いのですが、ここが2の倍数か3の倍数かみたいな条件で後者の書き方だと複数の条件が成立して何個もprintfされることもあります。いずれの書き方をしたにせよ、条件と処理の見通しが悪くなるという意味では同じです。

実はC言語の実装としては、switchはとても効率の良いコードを出してくれるのです。変数を評価した後に、その結果を使って次に処理する部分まで飛ぶ処理をテーブルやインデックスを使って何度も計算しないで済むようにしてあります。ですからcaseが100個あろうとも、事実上ひとつの処理で済んでしまうのです。もっとも今となってはこの効率はそんなに有効に働いていないのかもしれません。実は処理系によっては、結局、たくさんのifがあるのと同じような実装になっていることもあります。

C / C++ 言語のswitch文を最適化 (テーブルジャンプが使われるようにする)

この効率を求める場合、C言語であれば関数ポインタを活用することになります。FORTRANの割当型GOTO文に少しだけ似ていますが、関数ポインタ型の配列にcaseにあたる処理を行う関数へのポインタを並べて、この配列のインデックスを計算式として、呼び出し先を決めてしまうのです。もっともこの方法だと処理をそれぞれ関数にする必要がありますし、関数のポインタを取ると、その関数はインライン展開が出来なくなるので、処理単位が小さいとやぶ蛇になる恐れはあります。まあC言語の関数ポインタによる関数呼び出しは、構文が書きにくいことこの上ないので、可読性は悪くなるのは確かでしょうね。


最後の方は何を言っているのかさっぱりわからんという人も多いかと思いますが、条件によってそれぞれの処理がある場合(これはイベントループとかでよくあるのですが)、評価を何度もするのは宜しくないので、できれば多分岐の構文を適切に使うと良いことがあるよ。と覚えていただければ幸い。

ヘッダ写真は、以下より。
https://commons.wikimedia.org/wiki/File:Maschen_NS_010405.jpg

この記事が気に入ったらサポートをしてみませんか?