アセンブリ言語や機械語を書いたことのある人なら「ジャンプ命令」は知っていると思います。
「ジャンプ命令」とは、プログラムの実行位置(制御位置)を指定アドレスに移動する命令です。
アセンブリ言語では、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文というものです。
アセンブリ言語ではジャンプ命令でループを組みますが、つまり、ifとgotoがあれば、ループ文はなくてもプログラムは書けるということです。
しかし、どう考えてもwhile文やfor、foreachの方が見やすく、使い勝手が良いのは明らかです。
ですので、普通は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: {}
}たとえば、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文の中にあるcaseへgotoでジャンプすることができます。
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:ラベルまでジャンプします。