ブロックとスコープ

ブロック

{ }で囲まれた文のまとまりを ブロック と呼びます。 ブロックは一般的に、if 文で条件を満たした時の処理や、for 文や while 文での繰り返し内容を記述するのに使用されます。 if 文、for 文、while 分と組み合わせずに単独でブロックを作ることもできます。

if (true) {
  // ブロック
}

for (int i = 0; i < 10; ++i) {
  // ブロック
}

int i = 0;
while (i < 10) {
  // ブロック
  i += 1;
}

{
  // ブロック
}

スコープ

変数がアクセス可能な範囲を スコープ と呼びます。 グローバル変数のスコープはプログラム全体です。 ローカル変数のスコープは変数が宣言された関数またはブロック内です。 ただし、アクセスする前に変数が宣言されている必要があります。

例えば以下のプログラムには「↓」と「↑」で囲まれた ①〜⑦ の 7 つのスコープがあります。

// ↓①
float[] numbers = new float[100];

void setup() {
  // ↓②
  noLoop();
  for (int i = 0; i < numbers.length; ++i) {
    // ↓③
    numbers[i] = random(1);
    // ↑③
  }
  // ↑②
}

void draw() {
  // ↓④
  int n = numbers.length;
  for (int i = n; i > 0; --i) {
    // ↓⑤
    for (int j = 1; j < i; ++j) {
      // ↓⑥
      if (numbers[j - 1] > numbers[j]) {
        // ↓⑦
        float tmp = numbers[j];
        numbers[j] = numbers[j - 1];
        numbers[j - 1] = tmp;
        // ↑⑦
      }
      // ↑⑥
    }
    // ↑⑤
  }
  printArray(numbers);
  // ↑④
}
// ↑①

変数にアクセスできる場所の多さをスコープの広さと表現することがあります。 ① は ② よりもスコープが広く、② は ③ よりもスコープが広いです。 逆に、③ は ② よりスコープが狭く、② は ① よりスコープが狭いと言えます。

スコープが広いと、その変数がどのように使用されているかを読み取りにくくなります。 基本的には、できるだけ狭いスコープで変数の宣言を行いましょう。

上記のプログラムをローカル変数を使わずにグローバル変数のみで書くと以下のようになります。

float[] numbers = new float[100];
int i, j, n;
float tmp;

void setup() {
  noLoop();
  for (i = 0; i < numbers.length; ++i) {
    numbers[i] = random(1);
  }
}

void draw() {
  n = numbers.length;
  for (i = n; i > 0; --i) {
    for (j = 1; j < i; ++j) {
      if (numbers[j - 1] > numbers[j]) {
        tmp = numbers[j];
        numbers[j] = numbers[j - 1];
        numbers[j - 1] = tmp;
      }
    }
  }
  printArray(numbers);
}

このプログラムには以下のような問題があります。

  • どの変数がどこで読み取られるかわか李にくい
  • どの変数がどこで更新されるかわかりにくい
  • 変数 i が 2 つの異なる文脈で使われている

既にグローバル変数で使われている名前でも、ローカル変数で同じ名前をつけることができます。 しかし、スコープが異なっていても、同じ名前のローカル変数が既に宣言されている場合は同じ名前のローカル変数を宣言することはできません。 グローバル変数と同じローカル変数名とつけることを シャドーイング と呼びます。

以下のプログラムでローカル変数とグローバル変数のスコープについて確認してみましょう。

int a = 1;
int b = 2;

void setup() {
  noLoop();
}

void draw() {
  println(a, b); // ①
  int a = 3;
  println(a, b); // ②
  {
    println(a, b); // ③
    int b = 4;
    // int a = 5; // 既にローカル変数 a が存在しているのでエラー
    println(a, b); // ④
  }
  println(a, b); // ⑤
}

実行結果は以下のようになります。

1 2
3 2
3 2
3 4
3 2

① では、グローバル変数の ab の値が表示されます。 ② では、ローカル変数の a とグローバル変数 b の値が表示されます。 ③ では、ローカル変数の a とグローバル変数 b の値が表示されます。 ④ では、ローカル変数の a とローカル変数 b の値が表示されます。 ⑤ では、ローカル変数の a とグローバル変数 b の値が表示されます。

シャドーイングと変数の書き換え

ローカル変数を変更してもシャドーイングされているグローバル変数には変化が起きないことに注意しましょう。 以下のプログラムで動作確認してみましょう。

int a = 1;

void setup() {
  noLoop();
  println(a); // ①
  a = 2;
  println(a); // ②
  int a = 3;
  println(a); // ③
}

void draw() {
  println(a); // ④
  {
    println(a); // ⑤
    int a = 4;
    println(a); // ⑥
    a = 5;
    println(a); // ⑦
  }
  println(a); // ⑧
}

実行結果は以下のようになります。

1
2
3
2
2
4
5
2

① では、グローバル変数 a を参照しています。 ② では、2 に書き換えられたグローバル変数 a を参照しています。 ③ では、3 の値を持ったローカル変数 a を参照しています。 ④ では、グローバル変数 a を参照しています。a の値は setup 関数で 2 に変更されています。 ⑤ は、ブロック内になりますが ④ と同じです。 ⑥ では、ローカル変数 a を参照しています。 ⑦ では、5 に書き換えられたローカル変数 a を参照しています。 ⑧ では、ローカル変数 a のスコープを抜けたため、グローバル変数a が参照されています。

results matching ""

    No results matching ""