Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
第27回 配列
配列は昔からある技術です。新鮮さがないためにまったく注目されませんが、昔から今まで生き延びているということ自体がその重要性を証明しています。今回は地味な説明になりますがじっくりと読んで配列の概要を把握することは有意義です。次回取りあげるコレクションの理解にもつながります。
この回の要約 ・配列を使うと似たような変数をまとめることができる。 ・配列は Dim MyArray(3) As String のように宣言する。 ・配列は添字を変数にしてループをまわせる。 ・配列をコピーするにはCloneメソッドを使う。 ・動的配列を作成すると、Redim Preserveを使うことにより後からサイズを変更できる。 |
配列に関する説明をする前に少し今回の方針を書いておきます。
私は普段初級講座を書くに当たっては「VBにはどんな機能があるのか」だけを書くのではなく、「その機能を何に使うのか」もできるだけ紹介するようにしてきました。しかし、今回の配列に関してはなかなか「何に使うのか」の部分が思いつかず、結局配列という機能の説明しかしないようになってしまいました。
これは配列が重要じゃないのではなく、 配列によく似た機能でCollection(読み方:Collection = コレクション)と呼ばれるものがあるせいなのです。Collectionの方が便利で手軽なため配列の活躍できる一般的な例がなかなか思いつかないのです。ただし、個別の特別なシーンでは配列を使うことは珍しくありませんし、Collectionを扱う上でも基本的な配列の考え方は有用です 。
そのような事情で今回は味気ない構文の紹介のようになってしまっていまので、コーヒーでも飲みながらリラックスして一読してみてください。
なお、Collectionについては次回解説します。今回の配列と合わせて習得して理解を深めてください。
配列とは似たような変数をまとめて簡単に取り扱うための技術です。ただ単に簡単に扱えるようになるだけではなく配列を利用した独特の方法でプログラムを組むこともできます。
たとえば、次のように3つの数値型の変数を使うプログラムがあったとしましょう。
Dim
Number1 As Integer Dim Number2 As Integer Dim Number3 As Integer
Number1 = 101 MsgBox(Number1 + Number2 + Number3) |
■リスト1:普通の変数
このプログラムにでてくる3つの変数を配列としてまとめて次のように記述することができます。
Dim
Number(2) As Integer
Number(0) = 101 MsgBox(Number(0) + Number(1) + Number(2)) |
■リスト2:配列
見るとわかるように配列にすると変数の後ろにかっこがついたような書き方になります。
配列は普通の変数と同じように使用できますが、使用するときにはかっこの中に番号を指定します。これだけが普通の変数と配列の要素の違いです。
'普通の変数の読み書き Nation = "カンボジア" MsgBox(Nation) '配列の読み書き |
■リスト3:配列と普通の変数の比較
かっこの中の番号のことを添字(そえじ)またはインデックスなどと呼びます。添字はあらかじめ指定した範囲でなければなりません。
添字の範囲はDimなどで宣言するときに指定します。
Dim Nations(2) As Integer |
■リスト4:配列の宣言
この例のように (2) と宣言すると、0〜2までの3つの要素を持つ配列を宣言したことになります。つまり、0, 1, 2と3つの変数が一度に宣言されたような効果があるわけです。今「要素」という言葉を使いましたが、この「要素」という言葉も「配列」と並んで重要です。 また、この例の配列の場合、「配列のサイズは3である」のように「サイズ」という言葉を使うこともあります。「配列のサイズ」と言われたら通常はその配列の要素の数を表しています。
要素が3つくらいの配列なら別々に3つの変数を宣言してもさして手間はかかりませんが、要素が100個くらいになると配列のメリットがよくわかります。宣言するのが大変だという理由だけで配列を作成するケースはそれほど多くありませんが皆無ではありません。
配列を使用する場合の多くはプログラムでの個別の事情がある場合なので一般的な使用例はあまり思いつきません。一般的なケースではさきほど少し登場したCollectionを使う場合が多いのです。
ListBoxやCombobBoxなどのItems.AddRangeメソッドの引数には配列をセットすることができます。
次のように記述するとListBoxの項目を簡単にセットできます。
Dim
Nations(4) As String
Nations(0) = "カンボジア" ListBox1.Items.AddRange(Nations) |
■リスト5:AddRange
普通の変数と同じように宣言と同時に配列に内容をセットすることもできます。
この記述方法を使うと上記のプログラムはさらに次のように書くこともできます。
Dim
Nations() As String =
{"カンボジア", "ミャンマー", "ラオス", "ブルネイ", "パラオ"}
ListBox1.Items.AddRange(Nations) |
■リスト6:配列の初期化
このように宣言と同時に内容をセットする(=初期化する)場合は、配列のかっこの中は空にして、各値を { } でくくります。
これで自動的に配列Nationsは0 〜 4の5つの要素を持つ配列になります。
さらに、Newと { } を組み合わせて使えば、一々宣言することなしにプログラム中に直接配列を記述することができます。
次のようになります。
ListBox1.Items.AddRange(New String() {"カンボジア", "ミャンマー", "ラオス", "ブルネイ", "パラオ"}) |
■リスト7:プログラム中に直接配列を記述する。
この書き方は初めて見る方には奇異に感じられるでしょう。配列を直接プログラム中に記述する方法としてこのような書き方が特別に用意されているのです。覚えておくと何かと便利です。
既に、たくさんの宣言を1つにまとめられるという配列の利点を紹介しましたが、配列の最大の利点は添字を変数で指定できる点にあります。
たとえば、次のようにして配列に値をセットすることができます。
Dim
Numbers(99) As Integer Dim i As Integer For i = 0 To 99 Numbers(i) = i Next |
■リスト8:配列のループ
100個の変数にこのように簡潔に値をセットできるというのはすばらしい利点です。これは普通の変数では真似することができません。
宣言時に配列のサイズを指定しないこともできます。後にならないとサイズの数が決定できない場合や、サイズを変更する必要がある場合に使用します。このような配列を動的配列とよびます。対して、あらかじめサイズが決まっている配列を静的配列と呼びます。
動的配列を宣言するには、宣言するときにかっこの中を空にします。
Dim Names() As Integer |
■リスト9:動的配列の宣言
動的配列はサイズを決定しないまま要素にアクセスするとエラーになります。
Dim
Names() As String Names(0) = "後醍醐天皇" 'ここでNullReferenceExceptionが発生する。 |
■リスト10:間違った例
動的配列のサイズを決定するにはRedimステートメント(読み方:Redim = リディム)を使用します。次の例はサイズを3に設定してから要素にアクセスするのでエラーになりません。
Dim
Names() As String
ReDim Names(2)
Names(0) = "後醍醐天皇" |
■リスト11:単純なRedim
Redimを使用すると後から動的配列のサイズを変更することもできます。その時にPreserveキーワード(読み方:Preserve = プレサーブ)を指定しないと配列の内容はいったんすべてクリアされてしまいます。
そのため次のコードを実行すると空のメッセージボックスが表示されます。
Dim
Names() As String
ReDim Names(2)
'配列の要素数を3に決定
Names(0) = "後醍醐天皇" ReDim Names(3) '配列の要素数を4に変更 MsgBox(Names(0)) |
■リスト12:Redimは配列をクリアする。
Preserveを使用すると配列の内容はクリアされません。次の例では「後醍醐天皇」と表示されます。
Dim
Names() As String
ReDim Names(2)
'配列の要素数を3に決定
Names(0) = "後醍醐天皇" ReDim Preserve Names(3) '配列の要素数を4に変更 MsgBox(Names(0)) |
■リスト13:Preserveは配列を温存する。
後醍醐天皇は鎌倉時代末期から南北朝時代の天皇で、武家政権を打破し一時的に天皇親政を復活させました。これが建武の新政です。歴代天皇の中では雄略天皇に並んで非常に活動的だった天皇です。
Preserveはこのように便利に使用できますが、ループの中などで繰り返し使用すると体感できるほど処理が遅いです。
配列のコピーを作成する場合は注意が必要です。数値や文字列など変数と同じ感覚でコピーを作成すると思わぬ現象が発生します。次のコードを実行すると何が表示されるか考えてみてください。
Dim
Names1() As String =
{"後醍醐天皇", "カイ・ハンセン", "スタンダ−ル"} Dim Names2() As String Names2 = Names1 Names1(1) = "ガンジー" MsgBox(Names2(1)) |
■リスト14:配列のコピーのよくある間違い
このコードを実行すると「ガンジー」と表示されます。Name1(1)に「ガンジー」と代入する前に、Names2 = Names1 としているので、Names1(1)の値が「ガンジー」になろうがなるまいがNames2(1)の値は「カイ・ハンセン」であるかのうに思ってしまうかもしれませんがこれは間違いです。
実はNames2 = Names1という命令は「Names1の内容をNames2にコピーしろ」という意味ではなく、「Names1が見ているものをNames2も見るようにしろ」という命令なのです。つまり、Names1もNames2も結局は同じものを見ていることになるので一方の値が変更されれば、他方から見たときの値も変更されるというわけです。
もう少し丁寧に説明しましょう。はじめ、Names1とNames2は次のような状態になっています。
■画像1
ここで、Names2 = Names1 とすることで次のような状態になります。
■画像2
つまり、1つの実体を2つの別の名前の変数が共有している状態です。ですから、片方が値を変更すると他方でも値が変更されたことになるのです。
この現象はStringやIntegerなどでは発生しません。この辺りの詳しい事情はもう少し後で取り上げますのでさしあたっては配列のコピーの時に気を使ってください。この現象について自分でより詳しく調べる場合は「参照型」がキーワードとなります。
さて、単純に配列をコピーするには次のようにCloneメソッド(読み方:Clone = クローン)を使用します。
Dim
Names1() As String =
{"後醍醐天皇", "カイ・ハンセン", "スタンダ−ル"} Dim Names2() As String Names2 = Names1.Clone Names1(1) = "ガンジー" MsgBox(Names2(1)) |
■リスト15:配列のコピーの正しい例
この例では「カイ・ハンセン」と表示されます。
この例では Names2 = Names1.Clone が実行された時点で2つの変数は次のような関係になります。
■画像3
つまり、この場合Names1とNames2は何の関係もない別々の配列となります。 ですから、片方が値を変更しても他方ではまったく無関係です。
複数の添字を持つ配列を作成することもできます。添字が1つの配列は変数が順番に並んでいるようなイメージでしたが、添字が2つの配列は表のようなイメージになります。添字が1つの配列を1次元配列、添字が2つの配列を2次元配列と呼びます。3次元配列、4次元配列も作成できます。2次元以上の配列のことを多次元配列と呼びます。
添字と添字の間は「 , 」(カンマ)で区切ります。
2次元配列を使って1つの学年の全クラスの生徒の名前を管理する例は次のようになります。
Dim
Students(3, 30) As String
Students(0, 0) = "相原"
'1組の出席番号1番の人の名前 MsgBox(Students(2, 13)) '3組の出席番号14番の人の名前を表示する。 |
■リスト16:2次元配列
このプログラムが作成する2次元配列は次の表のようなイメージになります。
■画像4
実際にはこの例のようなプログラムを作成することはないでしょう。生徒を管理するプログラムをつくるのであればデータベースを利用することになります。この例のようにプログラムしたのでは組替えが発生したり、転入生・転校生があるたびにプログラムを変える必要があるので実用的ではありません。それに名前だけを管理するならExcelで十分です。
2次元配列を初期化(=宣言と同時に初期値をセットする)には次のように記述します。
Dim Numbers(,) As Integer = {{10, 2, 2}, {5, 6, 0}} |
■リスト17:2次元配列の初期化
この配列が2次元配列であることを示すために、宣言のかっこの中にカンマがひとつあることに注意してください。3次元配列の場合にはカンマを2つ書くことになります。動的2次元配列を作成する際にもこのように空のかっこの中にカンマだけを書く必要があります。
配列の正体はやはりクラスです。VBの世界ではほとんどのものがクラスや構造体なのです。配列はArray(読み方:Array = アレイ)という特殊なクラスです。このクラスはなんとNewを使ってインスタンス化したり、Inheritsを使って継承することができないという変わったクラスです。配列こそがArrayクラスのインスタンスなのです。
※初級講座ではInheritsによる継承は扱いません。
Arrayクラスは確かに変わっていますが、クラスである以上そのメソッドやプロパティなどのメンバを使用することができます。
Arrayクラスの主なメンバを表にまとめておきます。この他にもメソッドやプロパティは存在しますので興味のある方はMSDNライブラリで調べてみてください。
メンバ | 読み方 | 説明 |
Length | レンクス | 配列の要素の数を表します。 |
Rank | ランク | 配列の次元の数を表します。 |
Clone | クローン | 配列のコピーを作成します。 |
GetLength | ゲットレンクス | 特定の次元の要素の数を返します。 |
IndexOf | インデックスオブ | 前方から要素を検索します。 |
LastIndexOf | ラストインデックスオブ | 後方から要素を検索します。 |
Sort | ソート | 要素を並び替えます。 |
■表1:Arrayクラスの主なメンバ
それぞれ簡単に使い方を説明します。
Lengthプロパティは配列の要素の数を表します。多次元配列の場合はすべての次元の要素の数の合計を返します。特定の次元の要素数を取得するにはLengthプロパティではなくGetLengthメソッドを使用します。これらは配列の要素の数でループを実行するときなどに便利です。
Dim
Names() As String
= {"ネルヴァ", "トラヤヌス", "アントニヌス・ピウス"} Dim K As Integer For K = 0 To Names.Length - 1 ListBox1.Items.Add(K + 1 & " " & Names(K)) Next |
■リスト18:Lengthとループ
なお、これに似たVBの関数にUBound(読み方:UBound = ユーバウンド)があります。UBoundは配列の添字の上限を返します。第2引数で対象の次元を指定することもできますが、省略すると最初の次元の添字の上限を返します。
Dim
Names() As String
= {"ネルヴァ", "トラヤヌス", "アントニヌス・ピウス"} Dim K As Integer For K = 0 To UBound(Names) ListBox1.Items.Add(K + 1 & " " & Names(K)) Next |
■リスト19:UBoundとループ
Rankプロパティは配列の次元の数を返します。1次元配列では1を、2次元配列では2を返します。
Dim
Names() As String
= {"ネルヴァ", "トラヤヌス", "アントニヌス・ピウス"} MsgBox(Names.Rank) '1と表示されます。 |
■リスト20:次元数の取得
Cloneメソッドについてはさきほど紹介しました。
IndexOfメソッド、LastIndexOfメソッドは配列の要素を検索して最初に見つかった要素のインデックスを返します。IndexOfは前方から検索を開始し、LastIndexOfは後方から検索を開始します。値が見つからない場合はどちらも -1 を返します。以下の例は単純に検索する例ですが、検索開始位置などを指定することもできます。
Dim
States(5) As
String Dim Index As Integer Dim LastIndex As Integer Dim NoneIndex As Integer States(0)
= "カリフォルニア" Index = Array.IndexOf(States, "ニューヨーク") '2を返します。 LastIndex = Array.LastIndexOf(States, "カリフォルニア") '4を返します。 NoneIndex = Array.LastIndexOf(States, "兵庫") '-1を返します。 |
■リスト21:配列の検索
ところで、この例ではStates.IndexOfではなく、Array.IndexOfと記述することに注意してください。このメソッドは共有メンバなのでクラスから直接呼び出すことができるのです。もちろん、States.IndexOfと記述しても正常に動作しますがコードが読みにくくなります。どのように記述するかは最終的にはお好みです。
私はIndexOfメソッドを非共有メンバにして、Index = States.IndexOf("ニューヨーク")のように書いた方がスマートだと思うのですが、残念ながらマイクロソフトの技術者はそうは思わなかったようです。
Sortメソッドは配列の内容を辞書順に並び替えます。
Dim
Names() As String
= {"イノシシ", "ウマ", "アメンボ", "オットセイ", "エビ"}
'アメンボ, イノシシ, ウメ, エビ, オットセイの順に並び変わります。 ListBox1.Items.AddRange(Names) |
■リスト22:配列の整列
このメソッドにももっと複雑な使用方法が用意されています。2つの配列を連動させて並び替えることができるのです。
次の例をご覧下さい。
'国名と対応する首都名の配列を作成します。 Dim Nations() As String = {"日本", "アメリカ", "韓国", "イギリス"} '国名 Dim Cities() As String = {"東京", "ワシントン", "ソウル", "ロンドン"} '首都名 Dim K As Integer '国名を並び替えます。(連動して首都名も並び変わります。) Array.Sort(Nations, Cities) '結果を表示します。 |
■リスト23:整列の連動
実行するとちゃんと国名と首都名が正しく表示されます。もちろん国名は辞書順に並び変わります。
最後にもう1つの配列のループ方法について説明します。
配列とコレクションに関してはFor 〜 Nextの他にFor Each 〜 Next(読み方:Each = イーチ)を使ってループを行うことができます。
配列やコレクションのループでは要素をひとつひとつ処理していくことが多いのでFor Eachはそのために特に工夫されたループです。ループカウンタ用の変数を用意することなく直接要素を取り出すことができます。
次のように使用します。
Dim
Eras() As String =
{"縄文", "弥生", "古墳", "飛鳥", "奈良", "平安", "鎌倉"} Dim Item As String For Each Item In Eras ListBox1.Items.Add(Item & "時代") ListBox1.Items.Add("↓") Next |
■リスト24:配列のループ
この例を見れば使い方はすぐに分かっていただけると思いますが、念のためにもう少し説明します。
For Eachの後ろには要素を格納するための変数を指定します。ループ内ではこの変数を使って要素にアクセスすることができます。通常は配列の要素と同じ型の変数を用意します。たとえば、この例ではEarsが文字列型の配列ですから、Itemも文字列型です。
Inの後ろにはループの対象となる配列またはコレクションを指定します。
この例の形は定番なのでプログラマはみな暗記しています。