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

2.構造体

【目標】構造体の使い方をマスターして大量のデータを扱いやすくできるようにしましょう。

今回は「構造体」を扱います。 これは配列に似たようなものですが、 これを使えば分かりやすいプログラムを組むことができます。

※ 構造体の名前と変数の名前が同じだと紛らわしいと指摘を受けましたので、変数「persondata」を「pd」に変えました。 (2007.06.13)

'lesson1-4-2
#console
Type PERSONDATA
    name As String
    phonenumber As String
End Type
Dim pd As PERSONDATA
Dim i As Long
Print "ID", "名前", "電話番号"
Randomize
For i = 0 To 92
    pd.name = Chr$(i + &H21)
    'ここから3行は電話番号の生成
    pd.phonenumber = "0" + Str$(Fix(Rnd() * 9) + 1) + Str$(Fix(Rnd() * 9) + 1) _
        + Str$(Fix(Rnd() * 8) + 2) + Str$(Fix(Rnd() * 10)) + Str$(Fix(Rnd() * 10)) _
            + Str$(Fix(Rnd() * 10)) + Str$(Fix(Rnd() * 10)) + Str$(Fix(Rnd() * 10)) + Str$(Fix(Rnd() * 10))
    CallNum(i, pd)
Next
Sleep(3000)
End

Sub CallNum(ByVal id As Long, ByRef pd As PERSONDATA)
    With pd
        Print id, .name, .phonenumber
    End With
End Sub

実行すると次のようなウィンドウが表示されます。

C:\ActiveBasicCourse\lesson1-4-2.abp
ID      名前    電話番号
0       !       052614****
1       "       057316****
2       #       056522****

……

92      }       016807****
※1 本当は全部で93行表示されるのですが全部は書けないのでここでは省略しています。
※2 「*」の部分も本当は数字が1つにつき1文字入るのですが、電話番号として実在する可能性が非常に高いので伏せています。

では今回のプログラムの説明です。 今回も前回の配列に続き新しい概念があるので多少難しいと思いますが、最後までがんばって読んでみてください。

-*-*-*-*-説明-*-*-*-*-
【使い方】
(1) Type 構造体名  (変数の宣言)  End Type
(2) 変数  As 型名 
【意味】
(1) 構造体名 という構造体を使うことを宣言します。
(2) 構造体の中に変数名 という の変数を含めることを宣言します。
【備考】
(1) 構造体名 の後、End Typeの前にはそれぞれ改行が必要です。
(2) (変数の宣言) の中でこれを、一つの変数につき一行を使って宣言します。
【解説】
(1) 構造体の宣言(1)
(i) 構造体
 データの中には一つの変数名で複数の値を扱えるようにしたほうが便利なものがあります。 前回はそういうデータを扱う方法の一つとして配列を紹介しました。 復習しますと、一つの変数名の後に[要素番号 ]を付けることで同じ変数名なのに別の値を保存・参照することができるというものです。 またこの要素番号 には変数が使えるので、ループの利用によって一つの配列の全ての値に対して同じ処理をすることが可能であるということも学習しました。
 しかし、データの中には一つの変数名で処理した方がいいものの、配列で処理するには不向きなものがあります。 例えば名簿の場合、確かに一人につき、名前・年齢・住所・……というように複数のデータがあるわけですから、一つの変数名で複数の値を扱えた方が便利なデータです。 しかし、名簿の場合、名前は文字列、年齢は数字、……というように扱うデータの種類が違います。 配列は全て同じ型でなくてはならないのでよっぽど強引な方法をとらない限り使えません。 では、このような場合どうすればいいか――そこで使うのが「構造体」です。 「構造体」というのは一つの変数名に、複数──たとえ型が違う変数であっても──扱うことができるという便利なものです。 今回はこの「構造体」の利用について学習していきます。
(ii) 構造体の宣言
 配列の場合は一つの変数名で扱える変数は全てが同じ型なので配列の宣言ときに、その変数の型と扱う変数の個数を宣言すればよかったのですが、構造体のときは違います。 構造体の場合は、一つの変数名で扱える変数の型が違うため、配列のように簡単な宣言では使うことができません。 どういう変数の型を扱うのか、何個必要なのか……などのことをあらかじめ別に宣言しておく必要があります。
 ということで、ここではあらかじめしておく構造体の宣言について書きます。 宣言には、「TypeEnd Type」を使います。 まず、Typeの後に半角スペースを空け、続けて構造体名 を書きます。 構造体というのは、一つのプログラムで複数のタイプ(一つの変数名で扱うことのできる変数の型、個数のパターン)を宣言することができるので、それぞれのパターンに名前をつけておく必要があります。 その名前が構造体名 にあたります。 そして改行を入れて、その後、実際に使う変数を宣言していき(詳しい宣言の仕方はこの後します)、最後に構造体の宣言を終了する目印として「End Type」を書きます。
(2) 構造体の宣言(2)
 まず実際の構造体のイメージを持っていただくために、構造体のイメージ図を下に載せます。
構造体のイメージ図
 図のように一つの構造体にはいろいろな型(別に同じ型でもいい)の変数が従えていて、それぞれの変数には名前があります。 そして[4]でやりますが、これらの変数には「構造体名 .変数名 」というようにして参照します。
 では、実際に構造体で使う変数の型や個数の宣言をどうやるかというと、先ほど説明した「TypeEnd Type」の中で、その構造体で使う変数をDim宣言Dimがないバージョンという感じで行っていきます。
 冒頭のプログラムの場合は、String型の2つの変数「name」「phonenumber」を持つ「PERSONDATA」という構造体を宣言しています。
※ 構造体内部の変数はどんな型・種類でも使えます。 なのでLong型・Integer型・……などはもちろん配列変数なども使用できます。 配列の場合は「配列名 [最大要素番号 ] As  」という形でできます。
【使い方】
Dim 変数名  As 構造体名 
【意味】
変数名 で構造体名 が利用できるように宣言します。
【解説】
構造体の利用準備
(i) 先ほど構造体について長く説明しましたが、実際のところ「TypeEnd Type」は、あくまで構造体の設計図作りでしかありません。 設計図を作ったところで実際に作らなければ意味がありません。 ということでここでは実際に指定した変数名 で構造体が使えるようにする方法を学習します。
(ii) Dim宣言で の部分に使いたい構造体の名前を書くと、変数名 がその構造体として扱えるようになります。 つまりこの宣言をすると配列のように変数名 に、設計図どおりに「TypeEnd Type」で宣言された変数が作られ、従属するわけです。
※1 たとえ同じ構造体の設計図から、その構造体が使える複数の変数を作っても、それぞれの変数の中の変数は独立しています。 例えば「a」「b」「c」という3つの変数を持つ「ABC」という構造体の設計図を「TypeEnd Type」で作って、「Dim x As ABC」「Dim y As ABC」「Dim z As ABC」としたとします。 このときに「x」の中の「a」という変数に「1」を代入したとしても、「y」「z」の中の「a」という変数は何も変わりません。 同様に「y」の中の「c」という変数に「2」を代入したとしても、「x」「z」の中の「c」という変数は何も変わりません。
※2 今回は触れませんが、「Dim a[2] As ABC」のように構造体の配列を作ることもできます。 この場合、a[0]〜a[2]それぞれでABC構造体が使え、「a[1].c」のようなことができます。
【使い方】
Print 表示させるもの1 , 表示させるもの2 , ……
【備考】
表示させるもの を表示させたい分だけ「,」で区切りながら書きます。
【意味】
表示させるもの を、それぞれの先頭を揃えて表示します。
【解説】
「,」区切りのPrint文
( ) 今までに「;」区切りのPrint文を使えば続けて表示できると学習しましたが、これでは不便なときがあります。 それは「リストの出力」です。 「リスト」といえば名簿や家計簿のように一番上に項目の名前が書いてあり、その項目の列ごとに数値や文字が書かれているものです。 このようなリストを作ろうとして「『;』区切りのPrint文」を使おうとすると問題があります。 それは各項目が半角スペース1文字分しか空かないために列がずれてしまい非常に見にくいリストになってしまいます。 そこで使うのが「『,』区切りのPrint文」です。 これを使うと区切りが半角スペースの代わりに「タブ」になります。 なのでテキストを打つときにタブを打ったことがある方は分かると思いますが、タブをはさむと次の文字は前の文字の長さにかかわらず一定の場所まで送られます。 分からない方はメモ帳を開いて、「aaa[タブ]bbb[改行]cccccc[タブ]dddd[改行]」と打ってみてください([タブ][改行]の部分ではそれぞれ「タブキー」「エンターキー」を打ってください)。 実感が湧くと思います。
※1:詳しく言うと、「,」の次の文字は、「前の文字の場所(単位:文字目)より1以上大きい整数の中で最も近い8の倍数の次の場所」から表示されます。 この表現では分かりにくいと思いますので下に例を挙げます。
[例] Print "12345678", "abc"
"12345678"の最後の文字"8"は左から8文字目にあります。 よって「前の文字の場所」は「8文字目」、「それより1以上大きい整数」は「9以上」、その中で「最も近い8の倍数」は「16」、それの次の場所ということで「17文字目」から"abc"と表示されます。
※2:ここでの「文字目」というのは「半角文字列」を単位にします。 なので全角文字は「2文字分」として扱われます。
【使い方】
(1) 構造体宣言された変数名 .構造体内の変数名 
(2) Chr$(数字 )
【意味】
(1) 構造体宣言された変数名 の構造体内の変数名 にアクセスします。
(2) 数字 という文字コードの文字を生成します。
【解説】
(1) 構造体へのアクセス
( ) 今まで構造体の概念・構造体の宣言・構造体の準備をやってきましたが、ここでは実際にプログラムで構造体を変数として利用する方法を説明します。 といっても、使い方は簡単で、「構造体名」が使えるようにDim宣言された「変数名」の後に「.」(ドット)をはさんで、使いたい「構造体内の変数名」を書けば、それが一つの変数として扱われます。 例えば、今回のプログラムでは「PERSONDATA」構造体の中に「name」変数と「phonenumber」変数という二つの変数があります。 そして「pd」という変数で「PERSONDATA」構造体が使えるようにDimで準備されています。 この状況で「pd.name」とすれば、これは「PERSONDATA」構造体が使える「pd」という変数の中の「name」という変数を表します。 なので「pd.name = "abc"」とすれば「pd」の中の「name」という変数に"abc"が代入されます。 また「pd.name = "abc"」という状態で「Mid$(pd.name, 2, 1)」とすれば、"b"が取得できます。
(2) Chr$関数
( ) 以前にも何回かちらりと話したかと思いますが、コンピューター上で文字は全て数字として扱われます。 例えば「A」という文字には「65」という数字が、「B」には「66」という数字が当てられています。 このようにコンピューターでそれぞれの文字に当てられた数字を「文字コード」といいます。 このChr$関数は、この「文字コード」をもとの文字に戻す関数です。
[例] Chr$(65) → "A" (「65」が割り当てられている文字は"A"なので、"A"が返されます。)
※1:Chr$関数に渡せる数字は0〜255までです。 これを超える数字を渡されると、その数字を256で割った余りの文字コードが割り当てられている文字が返されます。
※2:日本語に使われる文字(全角文字)は実は0〜255の文字コードを2つ組み合わせいます。 例えば「森」という字は[144]+[88]という2つのコードを組み合わせて表現されます。 この組み合わせ方やアルファベットの"A"のときの[65]と日本語のときの[65]をどう区別するかとかを話しているとすごく長くなるので、ここでは深く言及しません。 疑問で仕方ない方は、杜甫々氏の「とほほのWWW入門」の中の漢字コードについてを読んでみてください。
【使い方】
( ) (行末に) _
【意味】
( ) 行末に「_」を書くと、その行とその次の行が、あたかも1行であるかのように実行されます。
【解説】
( ) 行をまたぐプログラム
( ) この付近の3行は電話番号を生成するための部分ですが、1行にするとあまりにも長くなるため、途中で分断した方が見やすくなると思いませんか? しかし、そのまま分断すると問題が起こります。 それは改行はプログラムの区切り目だからです。 この「プログラムの区切り目」とは簡単にいえば「代入・Print文による表示などの指令の1セットを区切る場所」です。 C系の言語をはじめとする言語は行末に「;」を打たないとそこをプログラムの区切り目と認識しませんが、ActiveBasicの場合は、改行をするだけでそこをプログラムの区切り目と認識してくれます。 それゆえ、途中に改行を入れるとその行と次の行はつながっていないと認識され、エラーが出てしまいます。 そこで1行を途中で分断するときは行末に「_」を打ってやります。 こうすることで、そこの改行はプログラムの区切り目と認識されずに2行に渡ってプログラムを書くことができます。
3行以上にまたぎたいときもまたいでいるプログラムの最後以外の行の末に全て「_」を打てば3行でも4行でも10行にでも分断できます。
【使い方】
( ) プロシージャ名 (構造体宣言済変数 )
【意味】
( ) プロシージャ名 に構造体宣言済変数 を引き渡します。
【解説】
( ) 構造体の引渡し
( ) 今度は構造体を関数などのプロシージャに引き渡すときの方法です。 プロシージャには1-4-2[7]で説明する方法でSub/Functionを宣言することで構造体をそのまま引き渡すことができます。 こうすることで複数のデータを短い記述で渡すことができます。 ということで、ここではどうやったら構造体をまとめて渡せるかをやるわけですが、書き方は非常に簡単です。 構造体が使えるように準備した変数(ここでは「構造体宣言済変数」と書きますが正式な用語ではありません)の名前を「.」以下を書かずにそのまま書けばできます。 例えば今回の例では「PERSONDATA」構造体を使えるようにした「pd」を丸ごと渡すために、引数のところに「pd」とそのまま書いています。 これだけで構造体宣言済変数が渡されたプロシージャでは、「pd.name」も「pd.phonenumber」も使うことができるようになります。
※ 今回のように構造体宣言済変数だけを書くと、実はそれはその変数の先頭アドレスを指すポインタになります。 ポインタ先頭アドレスの説明は以前にしたので省きますが、構造体というのは配列のときと同じで、先頭アドレスが分かれば、あとは構造体の宣言を見ることでそれぞれの変数の位置が分かります。 なぜかといえば、変数の型にはそれぞれ使用するバイト数が決まっていて(例えばLong型なら4バイト)、しかも構造体の宣言の順番に構造体の中の変数が並んでいるからです。 配列のときと同じようなものです。 なのでその構造体が宣言されていて、かつその先頭アドレスが分かれば各変数の位置が分かるわけです。 ということで、プロシージャの引数のところに先頭アドレスを表す「構造体宣言済変数だけの表記」をするのです。
【使い方】
( ) Sub/Function プロシージャ名 (ByRef 構造体引数名  As 構造体名 )
【意味】
( ) プロシージャ名 が構造体名が使えるように準備された構造体引数名 を受け取る準備をします。
【解説】
( ) 構造体の受け取り
(i) 前のところで引き渡し方を学習しましたが、ここでは引数を受ける準備の仕方を学習します。 普通変数を受け取るときには「ByVal/ByRef 引数名  As 引数の型 」というふうに書いて準備しますが、構造体を受けるときは「ByRef」を使い、引数名のところではそのプロシジャー内部で使う際の構造体宣言済変数名、そして型のところに構造体の名前(今回の例ではPERSONDATA)を書きます。
(ii) このようにして受け取った変数は、プロシジャー内で「構造体引数名 .構造体内変数名 」とすることでその構造体の内部の変数を実際に使うことができます。 今回のように「pd」という変数として受け取った場合は、「pd.name」のようにすれば渡された構造体宣言済変数「pd」の「name」変数を使うことができます。
(iii) また「ByRef」を使うことや、ポインタを渡すことから分かると思いますが、渡された構造体宣言済変数をプロシージャ内で変更すると、プロシージャの外に出てもその変更が効力を持ちます。 例えば今回のプロシージャでたとえると、もともと「pd.name」に"abc"という値が入っていたとしてもプロシージャ内で「pd.name = "123"」としてしまうと、プロシージャを抜けた後も「pd.name」は"123"という値になります。
【使い方】
( ) With 構造体宣言済変数名  (プログラム)  End With
【意味】
( ) WithEnd With内で構造体宣言済変数名 を省略することができます。
【備考】
( ) 構造体宣言済変数名 の後、End Typeの前にはそれぞれ改行が必要です。
【解説】
構造体宣言をした変数の省略
(i) さきほど実際に構造体を使うことを宣言した変数で、その変数の中の変数にアクセスするときは「pd.name」というようにすると書きましたが、何回も「pd」の中の変数を使おうとしたときにいちいち「pd」と書くのが面倒ではありませんか? そこで、ここでは構造体宣言済変数名 を省略する方法を学習します。
(ii) 構造体宣言済変数名を省略するには「WithEnd With」を使います。 Withの後に省略したい構造体宣言済変数名を書けば、その後の改行からEnd Withまでの間はその構造体宣言済変数名が省略できます。 そして省略した際の構造体の使い方は「.構造体内変数名 」と書きます。 「.」を忘れると構造体ではない普通の変数と認識されるので注意してください。
※ 「WithEnd With」の中で他の構造体を使いたいときは、普通に「構造体宣言済変数名 .構造体内変数名 」とすれば使えます。

【電話番号について】

今回のプログラムでは電話番号の自動生成をやっています。 この電話番号ですが、総務省のページなどを見ると、以下のように生成するとほぼ正しいものになるようです。 (注:今回のプログラムをくれぐれも悪用しないでください。)

1ケタ目2ケタ目3ケタ目4ケタ目5ケタ目6ケタ目7ケタ目8ケタ目9ケタ目10ケタ目
01〜91〜92〜90〜90〜90〜90〜90〜90〜9

うちの電話番号もこれで生成できます。 ひょっとしたらあなたの画面に表示されている番号は私のかも? (だからといって一件一件確認しないでくださいよ。 他の人の迷惑になりますから。 だいたい冒頭の例を"*"で伏せたのも誰かがイタズラでかけそうな気がしたからです。)

今回は今までの中で一番長いレッスンでしたが理解できましたでしょうか? 構造体は概念が多くてかなり複雑怪奇な文章になったと自分でも感じているのですが、どうでしょうか? もし分からなければ下にある掲示板へのリンクから質問でも文句でも何でも殴り書きしておいてください。 そうしないとどうも本当に分かっていただけているのかさっぱり分からないので……。

まあ、そんなこんなで、とにかくレッスンの最後のまとめ「やってみよう」です。

-*-*-*-*- -*-*-*-*-

あらかじめ今年の西暦をユーザに入力してもらい、その後、名前・生まれた年をユーザに入力してもらったら、名前とその人の年齢を表示するプログラムを作ってみましょう。 ただし以下の条件を満たしてください。

  • 名前と生まれた年をあらかじめ保存できる構造体を作って利用する。
  • 年齢の計算と表示はSubブロックの中で行う。
  • 表示の際にはタブ区切りを使う。
※ 年齢は現在の西暦からその人が生まれた年を引いて算出するようにすれば構いません。

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

前に戻る - 次に進む

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