OriosのActiveBasic講座(1-4):配列・構造体・ポインタ

今回は「配列」「構造体」「ポインタ」をやります。 これらは「効率の良いプログラム」と「高速で動作するプログラム」を作るのに必要なものです。 配列・構造体は共に変数を一つの変数名でまとめることができるもので効率のいいプログラムを組むことができます。 またポインタはActiveBasicをはじめとするBASIC系言語ではあまり使うことのないもので難しいものですが、使えるようになるとよりプログラムを高速化することができますし、コンピュータ内部にも詳しくなります。 ではOriosのActiveBasic講座、第四章へ入りましょう。

1.配列

【目標】配列の使い方をマスターし大量のデータをまとめて処理できるようになりましょう。

今回はデータをより扱いやすくするものの第一弾として「配列」を扱います。 これは数学でいう添え字(anのn)にあたるもので、これが使えると大量のデータを扱う際に一つ一つに変数名をつける必要がなく、また、まとめてデータ処理をすることができます。 さらに今回からは以前ちらっと説明した「ポインタ」というものが頻繁に出てきますが、これも今のABには重要なものなので少しずつ覚えていきましょう。 では早速次のプログラムを実行してみましょう。

'lesson1-4-1
#console
Dim kuji[3, 1] As Long
Dim mes[1] As String
Dim substr As String
Dim num As Long
mes[0] = "○"
mes[1] = "×"
substr = "00100111"
SubstArray(substr, kuji)
Randomize
num = Fix(Rnd() * 4)
Print "金運  : " + mes[kuji[num, 0]]
Print "仕事運 : " + mes[kuji[num, 1]]
Sleep(3000)
End

Sub SubstArray (ByVal substr As String, ByVal array As DWordPtr)
    Dim i As Long
    Dim str As String
    For i = 0 To 7
        str = Mid$(substr, i + 1, 1)
        array[i] = Val(str)
    Next
End Sub
【表示例】
C:\ActiveBasicCourse\lesson1-4-1...
金運  : ○
仕事運 : ×
※表示するたびに結果は違います

今回は「自動おみくじプログラム」ですがどうでしょうか? 「また占い系か……」という方もいらっしゃるかもしれませんが、私の好きな分野なのでご勘弁を……。 では、これの結果が良くても悪くても、説明に入りますので、しっかりと読んでくださいね。

-*-*-*-*-説明-*-*-*-*-
[1] Dim kuji[3, 1] As Long ─ 配列の宣言
【使い方】
Dim 配列名 [最大要素番号 ] As 型名
【備考】
○多次元配列のときはそれぞれの次元の最大要素番号 をコンマ(,)で区切ってカッコ内に記入します。
【意味】
(最大要素番号  + 1) 個の要素を持つ配列名 という配列を使うことを宣言します。
【前解説】
配列とは?
配列とは一つの名前に複数の変数が備わっている特殊な変数です。 この備わっている変数の一つ一つを要素といい、実際にプログラムを組むときには配列名 [要素番号 ]と書いてそれぞれの要素を区別します。 ただし要素番号は0から始まります。 例えば要素が3つある(=3つの変数が備わっている)「a」という配列変数があったとします。 その場合、それぞれの要素は「a[0]」「a[1]」「a[2]」というふうに表すことができ、それぞれの要素に違う値を持たせることができます。 また配列の要素を指定するときには変数も使うことができます。
また多次元配列というのもあります。 これは配列の各要素がさらに配列になっているようなもので、配列の階層が浅い順に「第一次元」「第二次元」……というように言います(注:最大255次元までしか作れません)。 例えば第一次元要素が3つあり、さらに第二次元要素が2つあった場合は、それぞれの要素はコンマで区切って「a[0,0]」「a[0,1]」「a[1,0]」「a[1,1]」「a[2,0]」「a[2,1]」と表すことができます。 これも一次元配列(最初に説明した配列)のときと同様、それぞれの要素に違う値を持たせることができます。 例えば先ほどの配列「a」の指定に値が「2」の変数「i」を使って「a[i]」とすれば「a[2]」の値を参照することができます。
【解説】
配列の宣言
先ほど配列について解説しましたが、配列を宣言するにはDimを使って宣言します。 宣言方法は通常の変数の宣言の際の変数 の直後、"As"の前に「[]」、そしてカッコの中には最大要素番号 を記入します。 この最大要素番号 は「要素数 - 1」で表される数で、要素を指定するときの一番大きい数字のことです。
[例] Dim a[2] As Long (この場合「a[0]」「a[1]」「a[2]」という3つの要素を持つ配列変数を宣言します。)
また多次元配列を宣言するときにはそれぞれの次元の最大要素番号を第一次元から順番にコンマ(,)で区切りながら、カッコ内に書きます。
[例] Dim a[2,1] As Long (この場合「a[0,0]」「a[0,1]」「a[1,0]」「a[1,1]」「a[2,0]」「a[2,1]」という3×2個の要素を持つ配列変数を宣言します。)
最大要素番号 の指定には変数・関数等は使用できません。直接数字で(=リテラル値で)指定してください。
[2] mes(0) = "○" ─ 配列変数の利用
【使い方】
配列名 [要素番号 ]
【備考】
○多次元配列のときはそれぞれの次元の要素番号 をコンマ(,)で区切ってカッコ内に記入します。
【意味】
配列名 の要素番号 の要素を指定します。
【解説】
配列の利用
宣言した配列を実際に利用するには、まず配列名 、続けてカッコ、そしてカッコ内に要素番号 を書きます。 もし配列が多次元の場合はそれぞれ次元の要素番号 を第一次元から順番にコンマで区切りながら記入します。 このようにして指定した配列は普通の変数と同じように使うことができます。
[例] a[0] = 3 (a[0]に3を代入)
c = b[2, 1] (b[2, 1]の値をcに代入)
a[2] = Fix(a[2]) (a[2]の値を切り捨てる)
配列の要素番号 を囲むカッコは丸カッコ("( )")、角カッコ("[ ]")のどちらでも構いません。
カッコ配列の要素番号 を囲むカッコに丸カッコ("( )")を使うとエラーが出ることがある(ActiveBasic4.0現在)ので、標準である角カッコ("[ ]")をご使用ください。(2005/05/12)
[3] SubstArray(substr, kuji) ─ 配列変数の引数渡し
【使い方】
プロシジャー名 (配列名 )
【備考】
○配列を受け取る引数のところに配列名 を書きます。
【意味】
配列名 そのものを引数として渡します。
【解説】
配列変数の引数渡し
サブルーチン関数で配列を指定したいときがあると思います。 そのときは配列名 だけを書いて渡せば配列そのものを渡すことができます。
[例] f(a) (配列が引数として必要な関数「f」に配列「a」を渡しています。)
メモリと配列
もう少し詳しく説明すると実は配列名 だけを書いた場合、これはその配列の先頭のアドレスを示します。 アドレスとは変数・配列などのメモリー上での場所を示す数値のことです。 アドレスや変数についてよく分からない方はもう一度変数のしくみを参照してください。 通常それぞれの変数はメモリー上のバラバラの場所に存在しています。 しかし配列は特別でそれぞれの要素が要素番号の小さい方から順番にメモリー上に連続して存在しています。
a[0]a[1]a[2]a[3]a[4]
配列変数の各要素のメモリー上での配置例
なので配列の先頭のアドレス(=上の例ではa(0)のアドレス)が分かれば、他の要素のアドレスも連続しているわけですからすぐわかります。 というわけで関数に配列そのものを渡すときはその配列の先頭アドレスを表す「配列名 」を渡すわけです。
[4] Sub SubstArray (ByVal substr As String, ByVal array As DWordPtr) ─ 配列変数の引数宣言
【使い方】
Sub/Function プロシージャ名 (ByVal 配列名  As 配列ポインタの型 ) As 戻り値の型
【備考】
(1)配列を受け取る引数を宣言する部分に「ByVal 配列名  As 配列ポインタの型 」を書きます。
(2)「As 戻り値の型 」の部分はそのプロシージャがFunctionのときのみに書きます。
【意味】
引数の配列名 を配列ポインタの型 として受け取ります。
【解説】
配列変数の引数宣言
引数に配列そのものを渡す方法は前の項目でしましたが、ここでは受け取る準備の方法を説明します。 まずさきほども書きましたとおり配列を渡すときはその先頭ポインタを渡します。 ところで変数(配列を含む)のポインタ自体を渡すときはByValを使用しなくてはいけません。 なのでこれも配列の先頭ポインタ自体を渡すわけですからByValを使用します。 つぎにプロシージャ内で使用する変数名ですが、ポインタにはポインタを対応させるということでプロシージャ内で使用する配列名 だけを書きます。 配列だからといってカッコなどはつけません。 そして一番重要なのは配列ポインタの型 です。 先ほどの項目で先頭アドレスが分かれば後の項目も分かるの書きましたが、実は他の要素のアドレスを求めるには一つ足りません。 それは「1つの要素の大きさ」です。 メモリーはバイト単位で数値を記録しますが、1バイトではたった256パターンの数値(通常0〜255または-128〜127)しか表せません。 しかし実際に使っている変数はLong型なら4294967296パターン(-2147483648〜2147483647)の数値が必要です。 なので通常256パターン以上の数値が必要な変数は複数バイトのメモリー領域を使って表現します。 ということは他の要素を参照するには1つの要素の大きさが何バイトあるかが分かる必要があります。 また実際に各要素に値の読み書きをするには「整数型か浮動小数点型か」も必要です。 その「1つの要素の大きさ」と「整数型か浮動小数点型か」という情報をどうすればいいかといえば、ポインタの型で指定するわけです。 以下にポインタ型とそれを使う変数の型の表を書いておきます。
配列ポインタの型使用する変数の型備考
DoublePtrDouble8バイト・浮動小数点型
SinglePtrSingle4バイト・浮動小数点型
DWordPtrDWord, Long4バイト・整数型
WordPtrWord, Integer2バイト・整数型
BytePtrByte, String1バイト・整数型
[例] ByVal a As DoublePtr (この場合Double型の配列変数「a」を受け取ります。)
[5] array(i) = Val(str) ─ プロシージャ内での多次元変数の利用
【使い方】
配列名 [合成要素番号 ]
【意味】
配列名 [合成要素番号 ]を参照します。
【解説】
プロシージャ内での多次元変数の利用
プロシージャ内でも一次元配列なら「a[1]」のようにしてプロシージャの外と同じように配列が利用できます。 これは先ほどから話しているとおり「先頭ポインタ」「1つの要素の大きさ」「整数型か浮動小数点型か」の情報がプロシージャに渡されているからです。 しかし多次元配列になると話が違ってきます。 まず多次元配列の各要素はメモリー上では次のように存在しています。
a[0, 0]a[0, 1]a[0, 2]a[1, 0]a[1, 1]a[1, 2]
Dim a[1, 2]」と宣言された配列の例
上のように最も右側の要素がまず0, 1, ……と順番に並び、その要素がその次元の最大要素数 までくるとその隣の要素が1増えて、また0,……、隣の要素がその次元の最大要素数 に達したら、そのさらに隣の要素が1増え……というふうに配置されています。 つまり多次元要素で各要素の場所を特定するのに「各次元の最大要素数」も必要になります。 しかしプロシージャに引数として配列を渡すときの情報や引数の宣言の際の情報に「各次元の最大要素数」に関する情報はありません。 なのでプロシージャ内では多次元配列をそのまま使えません。 ではどうするかというと「多次元配列を一次元配列で代用する」という方法を使います。 下の例を見てください。 上の多次元配列を一次元配列で代用した例です。
本来の多次元配列a[0, 0]a[0, 1]a[0, 2]a[1, 0]a[1, 1]a[1, 2]
一次元配列で代用した配列a[0]a[1]a[2]a[3]a[4]a[5]
分かりますか? 上のように次元関係なしに先頭から順に「0, 1, 2, 3,……」のようにすればいいのです。 これは要素の次元が変わっても「先頭ポインタ」と「1つの要素の大きさ」は変わらないからです。 今回はどのように配列が宣言されていたか分かったので直接「Fot i=0 To 7」という芸当ができますが、どのように宣言されたかが分からない場合はどうすればいいのでしょうか? その場合は「各次元の要素数」をプロシージャの引数にしちゃえばいいのです。 やり方は詳しく書きませんが、各次元の要素数から合成要素番号 を求めるアルゴリズムを載せておきます。
'次元数を代入
Dim n As Byte
'各次元の要素数(第a次元の要素数をElementAmount[a - 1]に代入)
Dim ElementAmount[255] As DWord
'各次元の要素番号(第a次元の要素番号をElementNumber[a - 1]に代入)
Dim ElementNumber[255] As DWord
'合成要素数が格納される変数
Dim CompositeElementNumber As DWord

Dim i As Integer
Dim j As DWord
Dim weight As DWord
For i = 0 To n - 1
    weight = 1
    For j = i + 1 To (n - 1)
        weight = weight * ElementAmount[j]
    Next
    CompositeElementNumber = CompositeElementNumber + weight * ElementNumber[i]
Next
[6] num = Fix(Rnd() * 4) ─ 乱数(Rnd関数)
【使い方】
Rnd()
【意味】
0以上1未満の乱数を戻り値として返します。
【解説】
乱数(Rnd関数)
「乱数」という言葉を聞いたことがありますか? 「乱数」というのは「次に出てくる数字が全く予測できない数」のことでサイコロをふって出てくる目などがそれです。 で、コンピューター上でも身近な例ではゲームなど、乱数が必要になるときがあると思いますが、それを可能にするのがRnd関数です。 Rnd関数の戻り値は0以上1未満(1は含まれない)の数字です。 この数字はRnd関数を使うたびに異なり、また次の数字を予測することも通常不可能です。 また0以上1未満の数字からある範囲の数字を取りたい場合(例えば0以上4未満の数字を取りたい)は、冒頭プログラムのようにその範囲をRnd()にかけてやれば取れます。
[例] Rnd() (この場合0以上1未満の数字が返ります。例えば0.35892511など)
Rnd() * 10 (この場合0以上10未満の数字を求めることができます。例えば3.5892511など)
Fix(Rnd() * 10) (この場合0〜9の整数を求めることができます。例えば3など)
[7] Randomize ─ 擬似乱数の初期化(Randomize命令語)
【使い方】
Randomize
【意味】
擬似乱数系列をWindows起動後の経過時間を利用して初期化します。
【解説】
擬似乱数の初期化(Randomize命令語)
先ほど「乱数は次の数字が予測できない」と書きましたが、実はRnd()関数には裏があります。 そもそも0か1というはっきりとしたコンピューターが適当な数字を返すはずがありません。 なのでRnd()関数というのは適当な計算をさせて次の乱数を求めています。 こういう乱数を「擬似乱数」といいます。 しかし擬似乱数は計算方法が決まっているので、n回目に取得したのRnd関数の値というのは何回やっても同じです。 これでは乱数の本来の役割が果たせません。 そこでこのRandomize命令語を使うのです。 Randmize命令語はWindows起動後の経過時間という使うときによって全然違う値を使って、Rnd関数の値が変わるようにします。 Rnd関数を使うときはあらかじめRandomize命令語を使うことをオススメします。

今回は主に「配列関数」について学習しました。 コンピューターで大量のデータを扱う際には絶対「配列」を使う方が効率のいいプログラムが作れます。 また今回からしばらくの間「ポインタ」という概念も出てきます。 この「ポインタ」というのはプログラミングを始めたばかりの人には関門だと言われています。 しかし「ポインタ」も一回扱うことができるようになれば効率がよく高速動作ができるプログラムが作れるようになります。 なので今回の「配列」と今回からしばらく登場する「ポインタ」はしっかりと扱えるようにしましょう。 毎回書いていますが質問・疑問・間違いの指摘・感想などは掲示板メールで遠慮なく言ってきてください。 あなたの理解にもなりますし、同じような疑問を持つ方にとっては参考にもなります。 では、最後の仕上げの「やってみよう」です。

-*-*-*-*- -*-*-*-*-
ユーザに1月・2月・3月それぞれの身長・体重を入力してもらい、3ヶ月の身長・体重の平均を求めるプログラムを作ってみよう。 ただし平均を求める部分では平均を求める関数を作ってみましょう。 (注:一次配列を使ってください。AB3.08a現在Input命令語で二次配列を使うとフリーズします。) AB4.0ではInput命令語に二次配列を使っても問題ありません。(2005/05/12)
【アドバイス】
  • 平均はForループを使ってみましょう。

ご質問・ご意見・ご感想・苦情等はメールまたはメイン掲示板のActiveBasic板などでお気軽にどうぞ。

前に戻る - 次に進む

ホーム > OriosのActiveBasic講座(トップページ) >