導入
構造化プログラミングの最終回として、
かつて、
この問題を上手に解決するためにプログラミング言語側に設けられた仕組みがスコープです。Processingなどのオブジェクト指向言語では、
展開
スコープとは
スコープとは、
ここでは変数のスコープに話を絞ります。メソッドやクラスについても同様に考え、
スコープを限定する方法
スコープを限定する最も基本的な方法は、
BasicScope.pde
。スコープを限定する方法の基本int x = 5;
{
int y = 3;
println("(x,y) = ("+x+","+y+")");
}
//スコープ外なのでエラーになります
//println("(x,y) = ("+x+","+y+")");
コメントアウトされているprintln
文をアンコメントアウトすると、y
を使用するのでエラーになります。
次のようにメッセージエリアへy
が見えません」
The field Component.y is not visible
[作業] sketch BasicScope.
を自分の手で入力して実行しましょう。コメントアウトしている行をアンコメントアウトして、
ブレースで囲まれたコードブロック内で宣言された変数は、
コードブロックを切り分けていれば、
明確にコードブロックを切り分けていれば、
SplitScope.pde
。コードブロックが別ならば同じ名前の変数が宣言できるint x = 5;
{
int y = 3;
println("(x,y) = ("+x+","+y+")");
}
{
float y = 6;
println("(x,y) = ("+x+","+y+")");
}
次のようにネストしたコードブロックでは、
NestedScope.pde
。内側のスコープで、int x = 5;
{
int y = 3;
println("(x,y) = ("+x+","+y+")");
{
float y = 6;
println("(x,y) = ("+x+","+y+")");
}
}
スコープを限定して変数を使っている例の代表は、for
ループのカウンタ変数です。次のサンプルコード
// Example 04-10 from "Getting Started with Processing"
// by Reas & Fry. O'Reilly / Make 2010
size(480, 120);
background(0);
smooth();
noStroke();
for (int y = 0; y <= height; y += 40) {
for (int x = 0; x <= width; x += 40) {
fill(255, 140);
ellipse(x, y, 40, 40);
}
}
2重for
ループを使ってディスプレイウインドウを円で埋め尽くします。x
とy
は円の中心座標のためだけの変数ですので他所で必要ありません。このコードのようにx
とy
を宣言すると、for
のコードブロック内ではy
のみ有効となり、for
のコードブロックの中ではx
とy
両方が有効となります。
つまり、
EX04_10_2.pde
。エラーを起こすGSWPのExample 04-10size(480, 120);
background(0);
smooth();
noStroke();
for (int y = 0; y <= height; y += 40) {
print("(x,y) = ("+x+","+y+")"); // エラー!xが使えません。
for (int x = 0; x <= width; x += 40) {
fill(255, 140);
ellipse(x, y, 40, 40);
}
}
print("(x,y) = ("+x+","+y+")"); // エラー!xもyも使えません。
これをwhile
文で書き換えると状況が少し変わります。
EX04_10_WithWhile.pde
。Example 04-10をwhile文で書き換えたsize(480, 120);
background(0);
smooth();
noStroke();
// 外側のループ
int y = 0;
while(y <= height){
// 内側のループ
int x = 0;
while(x <= width){
fill(255, 140);
ellipse(x, y, 40, 40);
x += 40;
}
y += 40;
}
Example 04-10と同じ動作をさせるためには、x
のスコープを外側のwhile
文のコードブロック内にしなければなりません。同様に、y
のスコープはこのスケッチ全体となります。for
文の利点は、
[作業] EX04_
を変更して、x
のスコープも変数y
と同じにしましょう。それでsketchの動きに変化があるでしょうか。エラーや問題が発生するでしょうか。エラーや問題が起きないとしたら、
スコープを限定しない方法
ある範囲のコードブロック全体で参照できる変数を、
CalcHookesLaw.pde
。グローバル変数の例double k = 5;
void setup(){
double x = 3;
println("k = " + k );
println("x = " + x );
println("F = " + hookesLaw(x) );
}
double hookesLaw(double l){
return k * l;
}
sketch CalcHookesLaw.
のコードの中で、k
は、
関数に引数として渡さずに使えますから、
・注意その1
物理定数など変更の可能性も必要性もない値で、final
で宣言されていませんから、
グローバル変数はなるべく使わない、
・注意その2
また、
CalcHookesLaw2.pde
。バネ定数が異なる場合のメソッドを別に作るdouble k = 5; //輪ゴム
double k2 = 2; //結束ゴムバンド
void setup(){
double x = 3;
println("k = " + k );
println("k2 = " + k2 );
println("x = " + x );
println("F1 = " + hookesLaw(x) );
println("F2 = " + hookesLaw2(x) );
}
double hookesLaw(double l){
return k * l;
}
double hookesLaw2(double l){
return k2 * l;
}
こんなコードは悪夢です。
こうなることは容易に推測がつきますから、hookesLaw
の引数はばね定数とばねの伸びを2つとも持つべきだったのです。
CalcHookesLaw3.pde
。関数呼出しがシンプルになったdouble k1 = 5; //輪ゴム
double k2 = 2; //結束ゴムバンド
void setup(){
double x = 3;
println("k1 = " + k1 );
println("k2 = " + k2 );
println("x = " + x );
println("F1 = " + hookesLaw(k1,x) );
println("F2 = " + hookesLaw(k2,x) );
}
double hookesLaw(double k, double l){
return k * l;
}
こうしてばね定数を引数で渡す仕組みにするならば、
CalcHookesLaw4.pde
。さらにコードを整理void setup(){
double k1 = 5; //輪ゴム
double k2 = 2; //結束ゴムバンド
double x = 3;
println("k1 = " + k1 );
println("k2 = " + k2 );
println("x = " + x );
println("F1 = " + hookesLaw(k1,x) );
println("F2 = " + hookesLaw(k2,x) );
}
double hookesLaw(double k, double l){
return k * l;
}
グローバル変数はなるべく使わずに済ませたいものです。
スコープを意識せず参照したい場合
変数は基本的に使用するクラスのオブジェクト内部で閉じたスコープにすべきです。これをインスタンス・
MathPI.pde
。スコープを意識せずに使えるPI定数//Java風に書くとこう。もちろんProcessingで動きます。
println("PI = " + Math.PI);
Processingでは、
PI.pde
。スコープを意識せずに使えるPI定数、println("PI = " + PI);
Java言語ではMathクラスのスタティックフィールドPIをクラス名から指定して使用するのが一般的ですが、
自分で作成したコードにおいても、
私はソフトの名称やバージョン番号などを定数クラスのstaticフィールドに持たせておき使っています。こうすると、
AppConstantsTest.pde
。グローバル変数ではなく定数クラスを使おうvoid setup(){
println("This application is <" + AppConstants.APP_NAME + ">");
println("Version " + AppConstants.VERSION);
}
class AppConstants {
static final String APP_NAME = "Macky!";
static final String VERSION = "1.0.2";
}
演習
演習1(難易度:middle)
GSWPのサンプルコードEx_
を読んで、x
, y
, px
, py
をdraw
メソッドのローカル変数にしなかった理由を説明してください。
Ex_05_09.pde
// Example 05-09 from "Getting Started with Processing"
// by Reas & Fry. O'Reilly / Make 2010
float x;
float y;
float px;
float py;
float easing = 0.05;
void setup() {
size(480, 120);
smooth();
stroke(0, 102);
}
void draw() {
float targetX = mouseX;
x += (targetX - x) * easing;
float targetY = mouseY;
y += (targetY - y) * easing;
float weight = dist(x, y, px, py);
strokeWeight(weight);
line(x, y, px, py);
py = y;
px = x;
}
演習2(難易度:middle)
sketch Ex_
は地上での体重を火星での体重に換算するものです。地球上での体重を火星での体重に換算する定数0.GravitationalAccelerationValues
)Ex_
としてください。
Ex_08_08.pde
// Example 08-08 from "Getting Started with Processing"
// by Reas & Fry. O'Reilly / Make 2010
void setup() {
float yourWeight = 132;
float marsWeight = calculateMars(yourWeight);
println(marsWeight);
}
float calculateMars(float w) {
float newWeight = w * 0.38;
return newWeight;
}
まとめ
- スコープの意味と活用方法を学習しました。
学習の確認
それぞれの項目で、
- スコープの意味が理解できましたか?
- 理解できた。自分のコーディングに活かす決心をした。
- 理解できた。しかし、
自分のコーディングに活かす必要を感じない。 - 理解できない。
- グローバル変数を避けるべき理由を理解できましたか?
- 理解できた。
- 理解はできるが必要を感じない。
- 理解できない。
参考文献
- 『Javaルールブック ~読みやすく効率的なコードの原則』
(電通国際情報サービス 監修、 大谷晋平、 米林正明、 片山暁雄、 横田健彦 著、 技術評論社) - Javaのコードを書く人は必携。ほぼ見開きでコンパクトながら意を尽くした解説がある。
- 『イラストで読むプログラミング入門』
(ダニエル・ アップルマン著, インプレス社) - プログラミングという技術をイラストレーションで分かりやすく解説している。例をふんだんに用いておりなるほどと思わされる。
演習解答
x
,y
,px
,py
をdraw
メソッドのローカル変数にすると、マウスポインタの位置で各変数の値を更新するのですが、 その回の draw
メソッドの呼出しが完了すると変数の値は破棄されます。前回draw
メソッドを実行した結果を保持するためには、変数のスコープを draw
メソッドの外にする必要があるのです。高速で繰り返し実行される
draw
メソッド内でたくさんの変数を宣言すると、その 「変数を宣言するための仕事にかかる時間」 がコンピュータの負担になることが考えられます。実行速度をわずかでも向上したい場合には、 ローカル変数を外に出すことも検討しましょう。しかし、 そのようなチューニングはコードの読みやすさが低下しますから、 なるべく選択するべきではありません。値の保持と言う問題が無ければ、 変数の数はわずか4つ5つですから、 すべてローカル変数にしても良いでしょう。