Skip to content

Instantly share code, notes, and snippets.

@k3kaimu
Last active December 18, 2015 13:08
Show Gist options
  • Save k3kaimu/5787336 to your computer and use it in GitHub Desktop.
Save k3kaimu/5787336 to your computer and use it in GitHub Desktop.
d-manual, 「007 その他の制御文」途中まで

その他の制御文

goto文とラベル

アセンブリ言語や機械語を書いたことのある人なら「ジャンプ命令」は知っていると思います。 「ジャンプ命令」とは、プログラムの実行位置(制御位置)を指定アドレスに移動する命令です。 アセンブリ言語では、for文とかforeach, while, ifなどの構造化文ありませんから、それらはジャンプ命令を使って実現します。

たとえば、次のD言語のコードを、Z80アセンブラで記述し、さらにD言語のコードに変換してみましょう。

size_t sum;
for(size_t i = 10; i != 0; --i)
    sum += i;
        LD A,0
        LD B,10
LOOP:   ADD A,B
        DJNZ LOOP
    size_t sum = 0;         // Z80アセンブラでのAレジスタ
    size_t cnt = 10;        // Z80アセンブラでのBレジスタ

  Lloop:                    // ラベル
    sum += cnt;             // ADD A,B
    if(--cnt)               // この2行は
        goto Lloop;         // DJNZ LOOPに相当

最後のD言語のコードで出現したLloop:goto Lloop;というのがラベルやgoto文というものです。 アセンブリ言語ではジャンプ命令でループを組みますが、つまり、ifgotoがあれば、ループ文はなくてもプログラムは書けるということです。

しかし、どう考えてもwhile文やforforeachの方が見やすく、使い勝手が良いのは明らかです。 ですので、普通はgoto文は使いませんし、むしろ嫌われています。

嫌われている理由というのは、goto文は、同一関数内では、ほとんどどこにでもジャンプできるからです。 たとえば、次のコードの実行結果を予想してみましょう。

import std.stdio;

void main()
{
    size_t cnt = 4;
    size_t index;

  LloopA:
    if(--cnt)
        writeln("A: ", cnt);
    else
        goto END;

    index = 0;

  LloopB:
    if(index == cnt)
        goto LloopA;
    else{
        writeln("\tB: ", index++);
        goto LloopB;
    }

  Lend:
    {}
}

このコードは次の等価なコードに変換できます。

import std.stdio;

void main(){
    foreach_reverse(cnt; 1 .. 4){
        writeln("A: ", cnt);
        
        foreach(i; 0 .. cnt)
            writeln("\tB: ", i);
    }
}

どうでしょうか?foreachを使ったほうがソースコードが見やすいですし、予想もしやすいかと思います。 このように、gotoはたしかに強力なのですが、プログラムの流れが破綻しやすく、期待した動作が得られなかったり、 後からソースコードを読むときに理解が困難になったりします。

  • TDPL

TDPLを読む限りでは、goto文は、変数宣言を飛び越えることやtry, catch, finallyへのジャンプはできないみたいです。

void main()
{
    goto Label;
    int x;
  Lable: {}
}

しかし上記のコードのように、現在のdmdの実装では変数宣言は飛び越えれてしまいます。

try, catch, finallyは、正しくコンパイルエラーを出してくれます。

void main(){
    //goto LtoTry;        // Error
    //goto LtoCatch;      // Error
    //goto LtoFinally;    // Error

    try{
      LtoTry: {}
        goto Lend;
    }
    catch(Exception){
      LtoCatch: {}
        goto Lend;
    }
    finally{
      LtoFinally: {}
        //goto Lend;      // Error
    }

  Lend: {}
}

switch文とcase文

たとえば、16進数の数値を表す文字列を受け取って、int型変数に数値として格納する処理を非現実的に書いてみましょう。

import std.stdio, std.string, std.array;

void main()
{
    auto str = readln().chomp();
    byte sign = 1;
    int value;

    if(!str.empty && str.front == '-'){
        sign = -1;
        str.popFront();
    }

    foreach(c; str){
        if(c == '0')
            value = value * 16;
        else if(c == '1')
            value = value * 16 + 1;
        else if(c == '2')
            value = value * 16 + 2;
        else if(c == '3')
            value = value * 16 + 3;
        else if(c == '4')
            value = value * 16 + 4;
        else if(c == '5')
            value = value * 16 + 5;
        else if(c == '6')
            value = value * 16 + 6;
        else if(c == '7')
            value = value * 16 + 7;
        else if(c == '8')
            value = value * 16 + 8;
        else if(c == '9')
            value = value * 16 + 9;
        else if(c == 'a' || c == 'A')
            value = value * 16 + 10;
        else if(c == 'b' || c == 'B')
            value = value * 16 + 11;
        else if(c == 'c' || c == 'C')
            value = value * 16 + 12;
        else if(c == 'd' || c == 'D')
            value = value * 16 + 13;
        else if(c == 'e' || c == 'E')
            value = value * 16 + 14;
        else if(c == 'f' || c == 'F')
            value = value * 16 + 15;
        else{
            writeln("Error !!!");
            break;
        }
    }

    value *= sign;

    writeln(value);
}

どう考えても、非効率的ですが、こういう時にswitch文は役に立ちます。

import std.stdio, std.string, std.array;

void main()
{
    auto str = readln().chomp();
    byte sign = 1;
    int value;

    if(!str.empty && str.front == '-'){
        sign = -1;
        str.popFront();
    }

    foreach(c; str){
        switch(c){
          case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
            value = value * 16 + (c - '0');
            break;

          case 'a', 'b', 'c', 'd', 'e', 'f':
            value = value * 16 + (c - 'a' + 10);
            break;

          case 'A', 'B', 'C', 'D', 'E', 'F':
            value = value * 16 + (c - 'A' + 10);
            break;

          default:
            writeln("Error !!!");
        }
    }

    value *= sign;

    writeln(value);
}

このように、switch文は、内部にcase文もしくはdefault文を持ちます。 switch文は、評価した式(整数型かenum型か文字列型)がcase <List><List>に含まれていれば、そのcaseまでジャンプします。 もし、一致するcaseが無ければdefaultまでジャンプします。 switch文はbreak文で脱出可能です。

また、以下の様な書き方も可能です。

import std.stdio, std.string, std.array;

void main()
{
    auto str = readln().chomp();
    byte sign = 1;
    int value;

    if(!str.empty && str.front == '-'){
        sign = -1;
        str.popFront();
    }

    foreach(c; str){
        switch(c){
          case '0': .. case '9':
            value = value * 16 + (c - '0');
            break;

          case 'a': .. case 'f':
            value = value * 16 + (c - 'a' + 10);
            break;

          case 'A': .. case 'F':
            value = value * 16 + (c - 'A' + 10);
            break;

          default:
            writeln("Error !!!");
        }
    }

    value *= sign;

    writeln(value);
}
  • switch文中のcaseへのgoto

switch文中では、そのswitch文の中にあるcasegotoでジャンプすることができます。

int x;
switch(x){
  case 0:
    goto case;

  case 1:
    goto case;

  case 2:
    goto case 4;

  case 3:
    goto case 8;

  case 4:
    goto case 3;

  case 5: .. case 10:
    goto default;

  default:
    writeln("Sw End");
}

goto case;は、その次のcase文までジャンプします。 goto case 4;case 4:までジャンプします。 goto default;とすると、default:ラベルまでジャンプします。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment