連載
» 2014年11月14日 18時43分 公開

TypeScriptで学ぶJavaScript入門:第7回 繰り返し処理(2):for/for ... in文 (2/3)

[羽山博,著]

for文でよくある間違い

 多少プログラミングの経験を積めば間違うことはないのだが、初心者に多い間違いに、for文の「()」の後にセミコロンを書いてしまうというものがある。セミコロンは文の終わりを示すので、for文の中で実行される「文 」として「何もしない」という文(空文)が指定されたものと見なされる。例えば、以下のプログラムを実行してみると、どうなるか考えてみてほしい。

for (var i = 0; i < 10; i++); {    // (1)
  document.body.innerHTML += "ピザ<br/>";
}



 実際にやってみると、「ピザ」が1回しか表示されない。(1)のfor文で空文が10回繰り返し実行された後、{ }の部分が実行される。{ }は単に複合文のブロックを作っているだけで、for文の範囲外であることに注意しよう。

 PlaygroundでJavaScriptにコンパイルされたプログラムを見ると、以下のようになっているので、期待した通りに動かないことが推測できる。

for (var i = 0; i < 10; i++)
  ;   // 空文
 {
  document.body.innerHTML += "ピザ<br/>";
}

for文の()の後に間違ってセミコロンを入れたプログラムのコンパイル結果(JavaScript)

[コラム]型推測とは

 TypeScriptでは、変数の宣言時にデータ型を指定しなくても、初期値を設定すればデータ型が推測されます。この機能を型推測と呼びます。例えば、サンプルプログラムでは、for文の中で制御変数iを宣言していますが、データ型は指定されていません。しかし、0を代入しているので数値型として扱われます。

 型推測によってデータ型が決められた変数に、異なるデータ型の値を代入しようとするとエラーになります。それでも、データ型を指定して宣言した方が間違いを防ぐのには役立ちます。ただし、for文の制御変数のように局所的にしか使われないことが確実に分かっている場合は、型推測を使った方が簡潔な記述になります。

 注意しないといけないのは、宣言時にデータ型も指定せず、初期値も代入していない場合です。そのような場合にはany型と見なされ、どのようなデータ型の値でも代入できるようになります(後で数値を代入したからといって、数値型にされるわけではありません)。何でも代入できるので一見便利なように思われますが、誤って異なるデータ型の値を代入してしまう可能性があるので、特別な理由がない限り、そのような書き方は避けた方がいいでしょう。

 以下の例は、最初に示したプログラムと同じ意味になります。

for (var i: number = 0; i < 10; i++) {
  …… 省略 ……
}


型推測を使わずに、データ型を明示して書いた例。問題はないが記述が少し冗長

 しかし、以下の例は、any型と見なされるので、変数iには文字列も代入できてしまいます。普通に考えると、変数iに文字列を代入することはないでしょうが、潜在的なエラーの可能性を持っていることは確かです。

var i;
for (i = 0; i < 10; i++) {


データ型も書かず、初期値の代入もしていないのでany型として扱われる例

 変数iをfor文の外で宣言する必要がある場合には、「var i」の後に「:number」と書き、数値であることを明示した方がいいでしょう。


for文をネストさせる

 for文の中にfor文を書くと「繰り返しの繰り返し」ができる。if文のときにも触れたが、このような形のコードをネスト(入れ子)と呼ぶ。for文をネストさせると、行列形式のデータを自然な感覚で取り扱うことができる。

 以下の例は、RPGゲームによくあるダンジョン(地下の迷宮)内の1つの部屋を表示するプログラムである。部屋の広さは10列×10行として、文字「.」が特に何もない床を表すものとする。

document.body.style.fontFamily = '"Courier", "monospace"';
for (var i = 0; i < 10; i++) {
  for (var j = 0; j < 10; j++) {
    document.body.innerHTML += "."// (1)
  }
  document.body.innerHTML += "<br/>";    // (2)
}



 1行目は、フォントを固定ピッチフォントにするための設定である。

 多少厳密さには欠けるが、for文をネストした場合は、内側の繰り返しが先に実行されると覚えておくといい。以下のような2桁のカウンターのイメージで捉えると分かりやすいだろう。

図4  for文のネストはカウンターの動きに似ている 図4  for文のネストはカウンターの動きに似ている
制御変数がどう変わっていくかを追いかけるよりも、カウンターのイメージで捉えると分かりやすい。

 イメージがつかめたところで、プログラムの処理を、以下の表で 順を追って細かく見てみよう。

制御変数i 制御変数j 動作
0 0〜9 制御変数iの値は最初0になっていて、制御変数jについて以下が行われる
・ 制御変数jの値が0から9まで変わる間、(1)の文によって「.」が1個ずつ表示される。従って、「.」は10個表示される
・ 制御変数jの値が10になると内側の繰り返しを抜ける
0 10 (2)の文によって「<br/>」が追加され、改行される
1 0〜9 次に制御変数iの値が1になり、制御変数jについて以下が行われる
・ 制御変数jの値が0から9まで変わる間、(1)の文によって「.」が1個ずつ表示される。従って、「.」は10個表示される
・ 制御変数jの値が10になると内側の繰り返しを抜ける
1 10 (2)の文によって「
」が追加され、改行される
……以下同様に繰り返される
ネストしたfor文の動作

 実行結果は以下の通り。

図5 ダンジョンの床を表示するプログラムの実行結果 図5 ダンジョンの床を表示するプログラムの実行結果
for文をネストさせれば行列形式のデータが直感的に取り扱える。

 さすがにこれだけでは味気ないので、部屋の中のランダムな位置にモンスターを1匹出現させよう。英字1文字がモンスターなどのキャラクターを表すものとする。本来ならばモンスターを表すクラスを作るなどして、汎用的なプログラムにするのだが、ここでは、できるだけコードを単純にしてある。

var m_name: string = "D";
var x: number = Math.floor(Math.random() * 10); // (1)
var y: number = Math.floor(Math.random() * 10); // (2)
document.body.style.fontFamily = '"Courier", "monospace"'
for (var i = 0; i < 10; i++) {
  for (var j = 0; j < 10; j++) {
    if (i == y && j == x) {     // (3)
      document.body.innerHTML += m_name;
    } else {
      document.body.innerHTML += ".";
    }
  }
  document.body.innerHTML += "<br/>";
}



 (1)でモンスターのx位置を決め、(2)でモンスターのy位置を決めている。x位置、y位置とも、0以上10未満のランダムな整数となる。for文のネストは最初の例とほとんど同じだが、(3)で制御変数iの値と変数yの値が等しく、かつ制御変数jの値と変数xの値 が等しいときだけ、モンスターの名前を表示する。それ以外の場合は「.」を表示する。

図6 ダンジョン内にモンスターを表示するプログラムの実行結果 図6 ダンジョン内にモンスターを表示するプログラムの実行結果
これだけではゲームにならないが、ネストさせたfor文の中でさまざまな処理ができることが分かる。

 次ページではfor ... in文について解説する。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。