プチコン3号入門講座
ジャンプ命令と繰り返し命令
プログラムというのは先頭行から順番に実行します。そして、最終行が実行されるかEND命令が実行されれば自動的に終了します。
しかし、これだと順番に1回実行したら終わりなのでゲームとしては成立しません。自分のキャラと敵のキャラが1回動いたら終了してしまうわけです。
そこで自由な行にジャンプすることができるGOTO命令が用意されています。
GOTOさんはどこでも一気に行ける代わりにそのための目印が必要になります。それがラベルです。「どこでもドア」で行き先を指定するのと同じです。
「キーボードから文字入力をしよう」で円の面積を求めるプログラムを書きましたが、これをGOTO命令を使って何度も繰り返して実行できるようにしたのが下記のプログラムです。
@LOOP
INPUT "はんけい=";R
S=R*R*PI()
PRINT "めんせき=";S
GOTO @LOOP
|
この最初にある@LOOPというのがラベルでGOTO @LOOPではこのラベルのある行(つまり、最初の行)にジャンプすることができます。
このラベル名は"@"の後に英数字および「 _ 」(アンダーバー)を使って自由な名前を付けることができます。
なお、ラベルは行頭だけではなく行の好きな場所に入れることが可能(命令と同じくマルチステートメントで記述が可能)ですが、行数を減らす必要性がある場合を除き無理にそのような使い方をする必要はありません。
また、GOTOによるジャンプは無条件で可能なのであちこちにジャンプしまくると処理の流れが掴みにくくなる(他のループ命令とは異なり@ラベルとGOTOは1対1で対応させる必要性がない)ためジャンプは必要最小限に止めておくのが良いでしょう。(GOTOであちこちにジャンプしまくったりして絡まりあった状態になったプログラムをスパゲッティプログラムと呼ぶことがある)
GOTOは無条件ジャンプだけではなくON 条件式 GOTO @ラベルという記述によって条件分岐させることが可能になります。
条件式の値が0の時は最初に記述のラベル、値が1の時は2番目に記述のラベル、・・・というように値によって複数のジャンプ先を変えることができます。
これは簡単に言えば、
IF A=0 THEN @LABEL0
IF A=1 THEN @LABEL1
IF A=2 THEN @LABEL2
IF A=3 THEN @LABEL3
IF A=4 THEN @LABEL4
|
という5つのIF命令の羅列がON A GOTO @LABEL0,@LABEL1,@LABEL2,@LABEL3,@LABEL4で済むようになるということです。(IF命令に関しては後述の「IF〜THEN〜による条件判断」を参照)
また、プチコン3号のヘルプを見るとON 制御変数 GOTOとなっていてONとGOTOの間には変数しか使えないイメージがある人もいるかもしれませんが、これは下記のFOR〜NEXTの終了値や増分量と同じく条件式が使えます。
@LOOP
(ループ内の処理)
ON X>99 GOTO @LOOP
|
このように記述することでX>99が成立するまで@LOOPとの間のループを繰り返す処理となります。(後述のREPEAT〜UNTILとほぼ同等の動作)
ON X>99 GOTO @LABEL0,@LABEL1と記述すれば、IF X>99 THEN @LABEL1 ELSE @LABEL0と同等の動作となります。GOTOでのジャンプ先を1つにしてON X>99 GOTO @LABELと記述した場合はIF !(X>99) THEN @LABELと同等の動作となります。(「!」は論理反転を意味していて「!(X>99)の時」というのは「X>99ではない時」という条件になる。詳しくは「論理式で深まる条件判断」を参照)
かなり便利な命令なので、ラベルが増えるのが嫌という人で無ければON〜GOTOは積極的に使って良いでしょう。
あと詳しくは後述の「DEFで自作関数を作ろう」で解説しますが、自作関数内ではラベルはすべてローカルラベルとなります。したがって、関数の外部にはGOTOを使ってジャンプはできません。しかし、プログラムスロットを指定してジャンプする場合に限り関数外のジャンプが可能になります。(例えばGOTO "1:@MAIN"とすればプログラムスロット1のラベル@MAINにジャンプする)
その場合は他スロットではなく現在動作しているスロットの関数外へのジャンプであれば「現在動作しているスロット」の取得が必要になります。1つの方法としてはVARが左辺値になるというのを利用した方法があります。詳しくは「VARをバリバリ使いこなそう」を参照してください。
GOTOを使ったジャンプは基本的には無条件であるため繰り返しは無制限に行われます。要するに無限ループになるわけです。その無限ループから脱出するためにはIF命令を使いループの脱出の条件を記述してループ外にジャンプするか、ON〜GOTOを使う必要があります。
実はそんなことをしなくても一定の回数だけ繰り返す命令はちゃんと用意されています。それがFOR〜NEXTです。
こうすることでFORとNEXTの間を10回繰り返すことができます。FORの後にあるI=1というのがカウンタとなる変数Iの初期値です。TOの後にある10が終了値です。
基本的にNEXTが1回実行されるごとにカウンタとなる変数の値がインクリメントされるためIの値が1から始まって10で終了するということは10回繰り返しになるわけです。
基本的にNEXTの後の変数名は省略できますが、その場合は直前に実行されたFORとの間をループします。
FOR I=1 TO 10
PRINT I ※ループの中はこのように字下げ(インデント)をすると見やすくなるのでオススメ
NEXT
|
こうすれば10回繰り返していることは一目瞭然ですね。
カウンタの変数の初期値は自由に設定できます。例えば"A"の文字コードは65、"Z"の文字コードは90であるため下記のようにすれば"A"〜"Z"の26文字を画面に表示できます。
FOR CHR=65 TO 90
PRINT CHR$(CHR);
NEXT
|
上記ではFOR〜NEXTでは1回繰り返すごとにカウンタの変数をインクリメントすると書いたのですが、それはTOの後にSTEPで増分量を記述すれば変えることができます。
FOR A=10 TO 0 STEP -2
PRINT A
NEXT
|
この例だと変数Aの値が10、8、6、4、2、0と減っていくので6回繰り返されます。
FOR I=1 TO 10 STEP -1
PRINT I ※Iの値は1→0と変化して終了値から遠ざかるためループ内は実行されない
NEXT
|
このようにループを実行すると終了値から遠ざかるような場合(STEPを省いた時だと初期値より終了値の方が小さくなるような場合)は後述のWHILE〜WENDのようにループ内は1回も実行せずに終了してしまうため注意が必要です。
またFOR〜NEXTは間に別のFOR〜NEXTを挟んで2重にすることもできます。
CHR=57856
FOR Y=0 TO 15
FOR X=0 TO 15
LOCATE X*2,Y:PRINT CHR$(CHR+Y*16+X)
NEXT
NEXT
|
さらにFOR〜NEXTを挟んで3重、4重・・・も可能ですがFOR〜NEXTからGOTOで抜けたり、GOTOでFOR〜NEXTの中にジャンプしたりすると誤動作を招いてしまう恐れがあるため注意しましょう。
《 ループからGOTOで抜けて別のループにジャンプする例 》
FOR I=1 TO 5
FOR J=0 TO 1
PRINT "もとのループ"
IF I>3 THEN GOTO @LABEL
NEXT
NEXT
END
FOR I=1 TO 10
@LABEL
PRINT "べつのループ"
NEXT
|
《 解 説 》
これを実行するともとのループと4回表示されてべつのループと7回表示される。
→ I=4ならば別のループにジャンプするため初期値4、終了値10で別のプールを実行する。
別のループであるFOR I=1 TO 10をFOR J=1 TO 10にするとべつのループと11回表示される。
→ I=4ならば別のループにジャンプするため初期値0、終了値10で別のプールを実行する。
FOR〜NEXTがどのような構造で動作しているかを熟知していればGOTOで抜けるというのは問題ありませんが、この解説を見なくても分かるような人を除きFORはNEXTで抜ける(どうしても強制的に抜けたい場合はBREAKで抜ける)ということを確実に行いましょう。
ここまではFOR〜NEXTは一定回数のループを実行する命令であるということを書きましたが、それはある意味正しくはありません。したがって、初心者には難しい部分もありますが、他のループ命令を使う際にも理解が早まるのでここで書くことにします。
それを説明する前にFOR I=0 TO 3.5みたいに増加するカウンタ変数の値が終了値ぴったりにならない場合のことを考えてみます。
このような場合にはどの段階でループを抜けるのかがよく分からない人もいることでしょう。これは端的に書くとFOR〜NEXTはカウンタとなる変数の値が終了値を超えた段階でループを終了します。さらに覚えておいた方がいいのはカウンタの加算処理はNEXTを実行時に行われるということです。
これはどういうことかというとFOR I=1 TO 10:NEXTという10回ループは変数Iの値が1、2、3、4、5、6、7、8、9、10と増えていくのですが、最初の「1」の時点ではNEXTが実行されておらず、そこから10回加算処理(NEXTの実行処理)が行われるためループを抜けた時点(10回目のNEXTを実行後)ではIの値は11になっています。
これは実際に自分の目で確かめてみてください。ちなみにループ10回目にGOTOやBREAKで強制的にループから抜けた場合にはIの値は10になるためループから抜けた時にIの値が11になるというのを知っておけば強制的に抜けたかそうでないかの判断も可能になります。
FOR〜NEXTの処理をより理解するため別の命令に置き換えて考えてみます。
FOR I=A TO B STEP C
(ループ内処理)
NEXT
|
この場合にFOR〜NEXTを後述のIF命令と上記のGOTO命令を使って書くと次のようになります。
I=A
@LOOP
IF C>0 AND I>B THEN @LOOPEND
IF C<0 AND I<B THEN @LOOPEND
(ループ内処理)
I=I+C
GOTO @LOOP
@LOOPEND
|
このように記述すればどのような状態になったらループを終了するのか、ループを終了時のIの値はいくつなのかというのが簡単に分かるようになると思います。(初期値が終了値を超えていたらループ内を実行しないためループ内処理が実行される前に条件判断が必要でその判断も増分量の符号で終了するための初期値と終了値の大小関係が変わるためそれも判断する必要がある。あとはループ内処理の後にNEXTで行う加算処理とループの先頭に戻る処理を入れれば完成)
また、これを見て分かるようにカウンタ変数、終了値、増分量はループ内で常に評価されているわけです。それによって、下記のようにFOR〜NEXTで自由な条件判断が可能になります。
STEPに記述する増分量が小数の場合には注意が必要なことがあります。それは「実数型の誤差による誤動作を回避するための方法」で書いているように小数は0.1のようなものであっても2進数では正確な表現ができないので誤差によってループ回数が本来のものとは異なってしまうためです。
例えば、FOR I=1 TO 2 STEP 0.1:NEXTというループがある場合は本来であれば変数Iの値は1、1.1、1.2、1.3、1.4、1.5、1.6、1.7、1.8、1.9、2と変化し11回目のループで終了になるはずです。しかし、カウンタ変数の加算処理の累積誤差によってループ10回目では本来ならば1.9になるはずだったのに正確には1.9000000000000008となっていてNEXTが実行されることで2.0000000000000009となり終了値である2を超えるため10回目のループで終了してしまいます。誤差が無ければ10回目のループでは変数Iの値は2となっていて終了値と等しいためさらにもう1回ループが行われます。
これを回避するには増分量には小数を使用しないというのがベターですが、どうしても使用しなければならない場合は事前にループ回数が何回かを確認しておくことが必要です。上記の場合だと終了値を2ではなく2.01にすれば想定通り11回ループさせることが可能になります。
さて、GOTOを使ったラベルジャンプならば無限ループは簡単に作れますが、ループ回数の設定可能なFOR〜NEXTも無限ループを作ることができます。使う機会はほとんどないと思いますが、FOR〜NEXTにはこういう使い方もあるというのを覚えておいても損はないかもしれません。
《 FOR〜NEXTを使った無限ループの作り方 》
STEP 0を使う |
FOR I=0 TO 1 STEP 0
NEXT
《解説》
増分量が0なのでカウンタとなる変数Iが0から1へと増えることはありません。(0に何回0を足しても0のまま)
|
毎回カウンタを初期化する |
FOR I=0 TO 1
I=0
NEXT
《解説》
I=0を毎回実行するのでカウンタが1に達することはありません。(I=0でなくてもSTEPによる増分量を考慮しても終了値以下になる値ならば何でも構わない)
|
プチコンの有効桁数を利用する |
FOR I=0 TO I+1
NEXT
《解説》
カウンタの値は1ずつ増えていきますが終了値も1ずつ増えているので永久にカウンタの値が終了値に達することがない・・・と言いたいところですが、プチコン3号で扱える数は有限です。
しかし、「変数とはどんなものか」で書いたようにプチコン3号の有効桁数は16桁未満です。したがって、カウンタの上昇は9007199254740992で停止します。
「ここでカウンタの変数Iが9007199254740992で停止するならばI+1も9007199254740992になるためカウンタの値と終了値の値が等しくなりそこでループが終了しそうなイメージがありますが、上記のようにFOR〜NEXTにおいてはNEXTを実行時に終了値を超えた場合にループが終了するため何回カウンタをインクリメントしても終了値を超えないこのループは無限ループになります。
|
このようにカウンタの変数や終了条件は変数を書き換えることで動的に変化させることが可能である(終了値や増分量はループ中は常に評価されている状態になっている)ためFOR〜NEXTは単純な繰り返し命令ではなく特定条件を満たしている間のみループを行う命令であるとも言えなくもないです。
その条件式をIF命令のように明示的にすることもできます。
《 条件付きFOR〜NEXTの例 1 》
FOR I=0 TO A>99
I=0
(ループ内の処理)
NEXT
|
《 条件付きFOR〜NEXTの例 2 》
FOR I=1 TO A>99
I=0
(ループ内の処理)
NEXT
|
これは条件式(この場合はA>99)が成立している間はそのループ内を実行するというものです。ちなみにカウンタ変数Iの初期値が0の時(例1)はループに入る段階で条件が成立してなくても1回はループ内を実行するのに対して初期値が1の時(例2)はループに入る段階で条件が成立してないとループ内は実行されず次の命令に移るという違いがあります
ただし、プチコン3号には条件式を使えるループ命令(WHILE〜WENDやREPEAT〜UNTIL)が用意されているためわざわざFOR〜NEXTでこのような書き方を覚える必要はありません。覚えておいた方がいいのはNEXTを実行時にカウンタの変数に増分量が加算されその時点で終了値となる値を超えていたらループが終了するということだけです。
RETURN (プチコン3号講座のページにもどる) RETURN *MAIN (トップページにもどる)