復習になりますが、ある一つの手続きを表すものを文(statement)というのでした。これに関連して、式(expression)というものがあります。
式と文は大体似たようなものなのですが、式には必ず値があるという点で文とは異なります。たとえば、変数a, bに対して、a +
bは、文ではありませんが、式です。また、変数aに対してa =
5という代入文は式でもあります。
また、1つ以上の文の集まりをブロック(block)といい、Luaの明示的なブロックの実装は次のようにするのでした。
ここから先の内容を理解するために、どうしてもしておかなければならない話がこれです。非常に重要なのでよく理解しておいてください。
注意:Luaの言語仕様や実装の関係上、Cなど一般的なプログラミング言語と一部異なる部分があります。
スコープ(scope)とは、ある変数などが特定の名前で参照できる範囲をあらわす言葉です。といってもピンと来ないでしょうから簡単な例を示します。
次の2つのコードはLuaと標準的なCで全く同じものを書いたものです。Cをやったことがある人は、対比しながら見てみるとよいでしょう。今後も適宜簡単なCコードとの対比でコードを提示することがあります。
いざこのコードを実行しようとすると、Luaのほうはエラーが発生して異常終了し、Cのほうはそもそもコンパイルが通らないので実行できません。
-- Lua a = 5 do local b = 12 b = a * b end c = a + b -- Error! attempt to perform arithmetic on global 'b' (a nil value) print("%d", c) |
// C/C++ #include int main(void) { int a = 5; { int b = 12; b = a * b; } int c = a + b; // Error! 'b' undeclared (first use in this function) printf("%d", c); return 0; } |
エラーの内容は、どちらも「bなんて変数はないよ!」というものです。
厳密に言うと、Luaは変数宣言が不要なので、代入文で値を入れない限り、どのような変数であってもnilという値に初期化されています。このエラーは実数型とnilの足し算をしようとしたことに対するエラーですが、細かいのでどうでもいいです。
「え、どうして?bあるじゃん!cは65が代入されて65って表示されるのじゃないの?」と思った人もいるかもしれません。ですが実際にはこのようにエラーが発生します(実際に書き写して実行してみるとよい)。
なぜそうなるのかという部分にスコープの話が絡んできます。
後の話をしやすくするために、"期待通りの"動きをする正しいコードを先に示しておきます。
-- Lua a = 5 do b = 12 b = a * b end c = a + b -- OK. c is 65 print("%d", c) -- out: 65 |
// C/C++ #include int main(void) { int a = 5; int b; { b = 12; b = a * b; } int c = a + b; // OK. c is 65 printf("%d", c); // out: 65 return 0; } |
最初の例では、do
endで挟まれたブロックを抜ける直前までは、bという変数に確かに60という値が入っています。
しかし、このブロックを抜けた途端、この変数bはきれいさっぱり跡形もなく処分されてしまいます。なんで?
その理由は、このブロック内で定義されている変数bが、ローカル変数として定義されているからです。
さて、ローカル変数という新しい言葉が出てきました。変数には2つ種類があって、プログラムの限られた場所からしか見えない(=使えない)ローカル変数と、プログラムのどこからでも見える(=使える)グローバル変数とがあります。
そして、ローカル変数の寿命(≒使える場所)を決めるのは、スコープです。
なんのこっちゃ、ですが、非常に重要なのでよく覚えておいてください。Luaでは、localキーワードをつけて宣言した変数はローカル変数とみなされます。
ローカル変数のスコープは、ローカル変数が宣言された行から、そのローカル変数が宣言されたブロックの終わりまでです。これは基本的にどんな言語でも同じで、Luaもその例に漏れません。
それぞれのローカル変数は、それぞれのローカル変数のスコープの内側では、自由に使うことが出来ますが、一度そのスコープを抜けてしまうと、途端に使えなくなります。
最初の例では、4行目に宣言された変数bがローカル宣言されているので、このブロック内でしか使えないのです。
よって、ブロックを抜けた7行目(c
= a + b)では、前のブロックで宣言された変数bはもう存在しておらず、期待通りの結果が得られなかったのです。
2つ目の例を見てみます。殆ど最初の例と変わらないのですが、たった一つだけ違う部分があります。それは、4行目にlocalキーワードがついていないという点です。
Luaでは、基本的にはどこで宣言しようが、localキーワードをつけない限り、グローバル変数となります。つまり、宣言した以降はプログラムのどこからでも使えます(実際には宣言する前から使えるが、nilが入っているだけなのであまり意味は無い)。
2つ目の例では、変数bはブロック内で最初に宣言されていますが、localキーワードがないので、この変数bはブロックを抜けた後も生き残り続け、使用することが出来ます。したがって、期待通り65という数字が表示されるのです。
まとめ
前置きが長くなりましたが、ようやく本題に入れます。
ふつう、プログラムというものを書いていると、「特定の場合だけこの処理をしたい」だとか、「このときはこれをする、あのときはあれをする」だとかいったことがよく出てきます。いわゆる条件分岐というものです。
Luaにもこのような処理を行う構文が存在します。
このまとまり全体をif節といいます。別にどうでもいいです。重要なのはif節の各部分の内容です。
if (条件式)の部分をif文といいます。このif文の条件式の部分に、条件判断をさせるための処理を書くわけです。
よく出てくるのが、ある数より小さい(大きい/等しい/等しくない)、とか、ある数の倍数、とか、nilではない有効な値が変数に入っているとき、といったものです。
thenキーワードからelseキーワードまではひとつのブロックで、thenブロックといいます。なお、thenキーワードは省略できません。Cとかをやったことある人は注意。条件式を満たす場合にここが実行されます。
elseキーワードからendキーワードまでもひとつのブロックで、elseブロックといいます。条件式を満たさない場合にここが実行されます。
なお、条件式が偽のとき、特に何も処理を行う必要がない場合には、elseブロックを省略することが出来ます。その場合、次のようにthenブロックの終わりはendキーワードになることに注意してください。
また、次のようにelseifキーワードを用いて複数の条件式を順番に評価することも出来ます。
elseifキーワードをたくさん使えば、もっと評価する条件を増やすことも出来ます。また、当然thenブロックなどの中にif節などの構造を入れ子にすることも出来ます。
条件式は、「ある条件が成立しているか否か」を評価するための式なので、普通、値は論理型であるべきです。つまり、条件式は評価した結果、値がtrueまたはfalseになるようなものであるはずですが、他のものも渡せます。
実は、条件式において偽と判定されるものは(式を評価した結果、)値がfalseまたはnilになるものです。それ以外のものはすべて真(true)とみなされます。
したがって、C/C++などのようにif(1)などと書くことが出来ます(Cと違い、if(0)はif(true)と等価なので注意)。
条件式には、ふつうは比較演算を伴う式を書きます。 ただし、すぐ上で述べたことから、nilを含む任意の値が入っている変数を直接渡すことも出来ます。簡単な条件式の例をいくつか挙げてみます。
なお、条件式が1つだけの場合、カッコは省略しても構いません。
なお、演算子のページで説明しましたが、「等しい」は=ではなく==です(=はプログラミングの世界では代入)。
また、多くのプログラミング言語と違い、「等しくない」は!=ではなく~=です。
Luaの論理演算は、常に短絡評価という方法で処理されます。短絡評価とは、論理演算子の左辺の値だけで式の結果が確定する場合、実際の演算を行わない評価方法のことです。
この短絡評価が適用される演算は、or演算とand演算です。
or演算では、どちらかの引数がtrueの場合、必ず結果はtrueとなるので、or演算子の左辺の値がtrueの場合、式の評価を行わずに左辺の値を返します。左辺がfalseまたはnilの場合、右辺の値を返します。
and演算では、どちらかの引数がfalse(またはnil)の場合、必ず結果はfalseとなるので、and演算子の左辺の値がfalseまたはnilの場合、式の評価を行わず左辺の値を返します。左辺がtrueの場合、右辺の値を返します。
変数に値がセットされていない(nil)場合、nilではない特定の値を既定値として設定したい場合があります。素直に書くと次のようになります。
ただ、これでは長いので、これと同等の結果を得られる次の文が慣用的に使われます。
なぜこれで同じ結果になるかというと、aが宣言されていないときは、aには暗黙的にnilが代入されているので、式 aor既定値 は、短絡評価によって右辺、すなわち既定値を返すためです。
ときには条件によって初期値を分けたいといった場合があります。そういうときに便利なのが、三項演算です。
Cなどには次のような?
:という三項演算子があるのですが、Luaにはこれに相当する演算子はありません。代わりに論理演算を使用します。
これと(ほぼ)おなじ処理を表すLuaの慣用表現は次のようになります。
なぜこれで同じ結果になるかというと、
1. 条件式が真のとき、まず短絡評価によってand演算は右辺の値(値1)が返され、次に値1が真の場合は短絡評価によってor演算では左辺である値1が返される
2. 条件式が偽のとき、まず短絡評価によってand演算は左辺の値(false)が返され、次に短絡評価によって、or演算では右辺である値2の値が返される
という2つの理由によります(冪演算子以外はカッコで評価順が指定されない限りすべて左から順に評価する)。
斜体で示しているように、この慣用表現は、値1がfalse(またはnil)の場合には使用できません。注意。
if(false)という構文があります。次のようになります。
if節のthenブロックは、if文の条件式がtrueのときに実行されるので、if(false)構文において、thenブロックの処理は常に実行されません。一見何の意味も持たないような構文に見えますが、thenブロックの中にデバッグ用処理を書くことでデバッグを容易にするという目的でよく利用されます。つまり、デバッグ時にはif(true)とするわけです。実際には次のようにデバッグ用のフラグを格納する変数を用意し、それを渡すことが普通です。
さて、プログラムを書いていると、たいていの場合、特定の処理を何度か繰り返したい、といったことが出てきます。Luaにはそのための構文がいくつか用意されています。
ifの時と同様、このまとまり全体をfor節と言います。 さて、for節について詳しく見ていく前に、簡単な実例を先に提示しておきます。
このプログラムは、1から10までの整数を順に足していき、都度その和を表示するプログラムです
。順番に見ていきます。
まず、i =
1の部分が初期化式になります。ここで定義された変数は、for節の中でのみ有効なローカル変数です。つまり、for節の外ではこの変数は使えません。この初期化式で初期化した変数をカウンタ変数またはループ変数と呼びます。
次に書かれてある定数10が終了値となります。for節の中の処理は、カウンタ変数がこの終了値を超えるまで繰り返されます。
最後に書かれてある1が加算値となります。for節の中の処理を1回行うごとに、カウンタ変数の値をこの加算値の分だけ増やします。なお、加算値は省略することができ、加算値を省略した場合は暗黙的に加算値は1とみなされます。
上の例を少し改造して、1から10までの奇数を足し、都度その和を表示するプログラムは次のようになります。
ループが5回回るとカウンタ変数iの値は11となり、終了値10を超えるのでこのforループはきちんと5回回ります。なお、終了値を9にしてもかまいませんが、11にするのはダメです。
いくつか練習してみましょう。
問題1 上の例を改造し、1から10までの偶数を順に足していき、都度その和を表示するプログラムを作れ。
問題2 1から100までの3の倍数をすべて表示するプログラムを作れ。
さて、forループが一番効果を発揮するのは、配列やテーブルのようなデータを操作するときです。次の例は、整数が入っている配列aに対し、aの全要素の和を求めるプログラムです。(配列aに値を入れる処理は割愛。いくつか値が入っているものとお考え下さい)
次の例では、forループを2つ入れ子にすることで、行列A, Bの和Cを計算します。(テーブルA,Bは、同じ大きさの2次元配列とします。)
問題3 2つ上の例を改造し、配列aに対してa[1]-a[2]+a[3]-a[4]+a[5]+…を計算し、その答えを表示するプログラムを作れ。
問題4 一つ上の例を参考に、l×m行列Aとm×n行列Bの積C=ABを計算するプログラムを作れ(行列の積の計算についてはここを参考にせよ)。