FreeBASIC マニュアルのトップに戻る

FreeBASIC TutIntroTypeDef

目次→教本→いっしょに学ぼうIntroduction to the Type Def←オリジナル・サイト

型の定義の紹介 左にメニュー・フレームが表示されていない場合は、ここをクリックして下さい

←リンク元に戻る プログラム開発関連に戻る

人事に関する記録や、ゲームで敵などの、集合構造を定義したいプログラムを作るときが、あるかもしれません。
個々のデータ型を使ってこれを実現できますが、プログラムの中で管理しにくいでしょう。
合成データ型を使うと、関連するデータ項目を、単一体として操作できる、ただ一つの構造に、一緒に寄せ集めることができます。
FreeBasic は、2つの合成データ型、TypeUnion(共用体) を提供します。

Types (型)

FreeBasic は、いくつかのデータ型を、型定義と呼ばれる統一的構造に集めることができるようになっています。型定義は、これらの集合体データ構造を描写するために使用できます。

型定義の基本構造は、下の通りです。

Type typename
    Var definition
    Var definition
    ...
End Type


Type-End Type ブロックは、定義の範囲を定義します。
Dim を使わずに、Dim キーワードを使うのと同じ方法で、型構造の要素を定義します。
下の短いコードは、従業員型を造る方法を示しています。

Type EmployeeType
    fname As String * 10
    lname As String * 10
    empid As Integer
    dept As Integer
End Type   


ポインタと他の型定義を含むデータ要素として、サポートしているデータ型のいずれも使用できます。
上記の例などの、型定義を作成するとき、コンパイラのために、ただテンプレートを作成しています。
型定義を使うために、型の変数を作る必要があります。下の短いコードを参照下さい。

Dim Employee As EmployeeType


いったん型の変数を作ると、ドット記法 var_name.field_name を使っている型の中で、各要素にアクセスできます。

上記の例を使って、fname 項目にアクセスするのに、以下を使用します。

Employee.fname = "Susan"


使い方
一度に複数の項目にアクセスするために、With-End With ブロックを使用できます。
下の短いコードは、上記の例で、With ブロックを使う方法を示しています。

With Employee
    .fname = "Susan"
    .lname = "Jones"
    .empid = 1001
        .dept = 24
End With   


コンパイラは、自動的に、変数 Employee を、With ブロックの中の個々のデータ要素に、縛ります。
これは、多くのキー入力操作が必要ないだけでなく、構造が最適化されていることを意味していて、そのままのドット表記法を使うより、少し速いです。

サブルーチンと関数に型を渡す
プログラムで型を使う 1つの利点は、構造を、サブルーチンや関数に渡して、全体として構造を操作できることです。
下の短いコードは、サブルーチン定義の一部を示しています。

Sub UpdateEmployeeDept (ByRef Emp As EmployeeType)
    .
    .
    .
End Sub


パラメタは、ByRef と共に限定されていることに、注意してください。
これは、サブルーチンの中で型をアップデートしようとしているので、重要です。
2つのパラメタを渡す方法 ByRefByval が、FreeBasic にあります。

Byref と Byval: 簡単な序論
Byref と Byval は、コンパイラに、引数をサブルーチンや関数に渡す方法を、伝えます。
Byref、または By Reference を使うと、ポインタ参照を、パラメタに渡します。そして、sub か func の中のパラメータの変更は、すべて、渡された実際の変数に反映されます。
言い換えれば、Byref パラメタは、メモリの実際の変数を示します。

一方、Byval、または By Value は、パラメタのコピーを作ります。そして、sub か func の中の変更は、いずれもローカルであり、渡された実際の変数に、反映されません。
Byval パラメタは、変数のコピーを指して、実際の変数それ自体ではありません。

FreeBasic .17 のデフォルトは、Byval を使ってパラメタを渡すことです。
渡されるパラメタを変えるためには、Byref 規則を指定する必要があります。
この例では、サブルーチンは、従業員型の部門 ID を更新します。このため、パラメタは、サブルーチンが型変数の dept 項目を更新できるように、Byref として指定します。
(編集者注:Byval は FreeBASIC の現在のバージョン(1.05〜) ではデフォルトのままです。)

一方、下のコードのように、型を更新する必要が無い場合もあります。

Sub PrintEmployeeRecord (Emp As EmployeeType)
    .
    .
    .
End Sub


この sub では、画面かプリンタに、従業員記録を表示するだけです。型変数に、変更の必要は何もありません。
ここでは、デフォルトの Byval が使われて、変数の参照ではなく、従業員記録のコピーを、Sub に渡します。
この場合は、Byval を使っても、あなたが変えないつもりの型変数に、偶然変更を加えることは、ありません。

パラメタ・データを変えるつもりがある場合にだけ、Byref を使用すべきです。
パラメタ・データを必要として、データへの偶然の変更を防ぎたい場合に、Byval を使うことは、はるかに安全です。
これらの偶然の変更は、プログラムで、見つけにくいバグを引き起こします。

型の中の型
組込みデータ型に加えて、型項目は、また、型定義に基づくことができます。
あなたはなぜこれをしたいでしょうか?
1つの理由は、データの抽象化です。
データ構造が一般的であれば一般的であるほど、さらにプログラムの他の部分で、コードを再利用できます。
書かなければならないコードが少ないほど、プログラムにエラーが入り込む機会は減ります。

Employee の例を使って、部門 id に加えて、より多くの dept 情報を対象にする必要があることを、想定してください。
部門のマネージャー名、フロアやビルなど、部門の場所、部門の代表電話番号、などです。
この情報を、別の型定義に入れることによって、あなたは、この情報それ自体を使えるようになります。また、Employee 型はもちろん、これとは別の型定義の一部としても、この情報を使えるようになります。
データ構造を汎用化することによって、プログラムは、より小さく、より健常になるでしょう。

型の中で型を使うことは、組み込みデータ型の1つを使う場合と同じです。
下の短いコードは、拡張した部門型と、更新された従業員型を示しています。

Type DepartmentType
    id As Integer
    managerid As Integer
    floor As Integer
End Type       

Type EmployeeType
    fname As String * 10
       lname As String * 10
        empid As Integer
        dept As DepartmentType
End Type

Dim Employee As EmployeeType


Employee 定義の中で、dept 項目が、組み込みデータ型の1つとしてではなく、DepartmentType として定義されていることに、注意してください。
Employee 型の中の、部門の情報にアクセスするとき、dept 項目にアクセスするために、合成ドット記法を使います。

Employee.dept.id = 24
Employee.dept.managerid = 1012
Employee.dept.floor = 13


型定義のトップレベルが、Employee なので、Employee 参照が、一番上になります。
ここで、dept がまた、型定義なので、DepartmentType の中の個々の項目にアクセスするために、dept 識別子を使う必要があります。
Employee は、従業員型について言及します。そして、dept は、部門の型について言及します。そして、id、managerid、floor は、部門の型の中の項目です。

型の中の型の中に、型を含めることによって、これをさらに深めることさえできます。
単純に、必要に応じて、型レベルを追加したドット記法を使うだけです。
入れ子にした型定義のレベルに、制限がなくて、いくつかのレベルで使われるとき、少し扱いにくくなります。

With と、入れ子にされた型
With ブロックを入れ子にして、入れ子にされた型を持つ With-End With ブロックを使用できます。
下の短いコードを参照下さい。

With Employee
       .fname = "Susan"
        .lname = "Jones"
        .empid = 1001
        With .dept
            .id = 24
            .managerid = 1012
            .floor = 13
        End With
End With


二番目の With が、型定義の次のレベルを指定するために、点表記法 (.dept) を使っていることに、注意してください。
入れ子にした With ブロックを使うときには、すべての End With 命令文が、その正しい With 命令文と対応していることを、確認してください。コンパイル・エラーを避けるためです。

型の代入
さらにデータ抽象化の考えを広げて、部門型の初期化を、従業員型の初期化と、切り離すことができると、よいでしょう。
2つの機能を切り離すことによって、必要に応じて、容易に追加部門の情報を加えることができるようになります。
これは、あなたが型代入を使用できるところです。

複数の型変数が、同じ型定義を共有するなら、 ちょうど、1つの組み込みデータ型を、別のものに代入できるように、1つの型変数を、別の型変数に代入することができます。

下の短いコードは、部門の初期化関数を概念化しています。このコードは、結果を、Employee 型の中の部門の型に、代入します。

'この関数は、dept 型に初期化して、それを呼んだプログラムに返します'
Function InitDept(deptid As Integer) As DepartmentType
    Dim tmpDpt As DepartmentType

    Select Case deptid
        Case 24 'dept 24
        With tmpDpt
                    .id = deptid
                    .managerid = 1012
                    .floor = 13
            End With

        Case 48 'dept 48
             With tmpDpt
                    .id = deptid
                    .managerid = 1024
                    .floor  = 12
                End With

        Case Else 'In case a bad department id was passed
                With tmpDpt
                    .id = 0
                    .managerid  = 0
                    .floor  = 0
                End With

    End Select

    'dept 情報を返します
    Return tmpDpt
End Function

'型のインスタンスを作成します
Dim Employee As EmployeeType

'Employee 型を初期化します
With Employee
    .fname = "Susan"
    .lname = "Jones"
    .empid = 1001
    .dept = InitDept(24) 'dept 情報を取得
End With


お分かりのように、上のコードでは、従業員型の dept 項目は、関数呼び出しで、初期化されます。
InitDept 関数は、DepartmentType を返します。そして、コンパイラは、Employee 記録の dept 項目に、その型を、代入します。

簡単な関数をプログラムに追加するだけで、プログラムの維持が、より簡単になります。
新しい部門が創設されるなら、InitDept 関数を、新しい部門の情報で、単純にアップデートして、再コンパイルできます。これで、プログラムは実行する準備ができています。

ビット項目

型定義で使用できるもう1つのデータ型、ビット・フィールドがあります。
ビット・フィールドは、variable_name: bits As DataType として定義されます。
変数名の後には、コロン、ビット数、そして、データ型が続かなければなりません。
ビット・フィールド内では、整数型(2つの浮動小数点型 'single'と 'double'を除くすべての数値型。32ビット開発の場合は64ビット型を除く。)のみが許可されます。
ビット・フィールドは、ブール型情報を追跡する必要がある場合に便利です。
ビットは、 01 のいずれかになり、Yes か No、On か Off、さらには 黒か白 を表します。

下の短いコードは、ビット項目定義を、示しています。

Type BitType
    b1: 1 As Integer
    b2: 4 As Integer
End Type


b1 は、1ビットと定義されます。そして、b2 は、4ビットと定義されます。
個々のビットを、型項目に渡すことによって、ビット項目を初期化します。

myBitType.b1 = 1
myBitType.b2 = 1101


ビット項目のデータ型によって、ビット項目で宣言できるビット数が決まります。
整数( integer )は、32/64ビット長(32/64bit システムで)なので、項目に最大 32/64 ビットを宣言できます。
しかし、ほとんどの場合、項目ごとに1ビットを宣言し、項目の数を使って、使用するビット・マスキングを定義します。
1つのビットを使うと、ビットが設定されているか消去されているかを判断するために必要なコーディングが簡素化され、型定義の中でビットの意味を簡単に識別できます。

項目の属性
型定義の変数を作成すると、型はメモリで水増しされます。
パディング(埋め草)は、より速く、型メンバーに、アクセスできるようにします。型項目が、4バイトか Word 境界で並べられるからです。
しかし、これは、パディングされていないファイルから、型記録を読もうとするとき、問題を起こします。
項目の属性を使って、型定義のパディングを変えることができます。

field キーワードは、型名の直後に使います。そして、1バイトの配置(水増ししない)に対して 1を、2バイトの配置のために 2を、そして、4バイトの配置のために 4を、持つことができます。
水増しせずに型を定義するためには、下の構文を使います。

Type myType Field = 1
     v1 As Integer
    v2 As Byte
End Type


2 バイトの配置のためには、field = 2 を使います。
field = property が割り当てられないと、パディング(埋め草)は、4バイトになります。
デフォルト配置を使って、FreeBasic によって作られた型定義を読んでいるなら、項目の属性を使う必要はありません。

Quick Basic 型の記録を読むときは、field = 1 を使う必要があります。QB は、デフォルトでバイト配置を使うからです。


型の初期化
型を Dim するときに、ちょうど組み込み変数に初期値を設定するように、型定義を初期化できます。
下の短いコードは、この構文を示しています。

Type aType
        a As Integer
        b As Byte
        c As String * 10
End Type

Dim myType As aType => (12345, 12, "Hello")


Dim 命令文では、矢の演算子 => は、コンパイラに、型変数を初期化している、と伝えるために、使われます。
型要素値は、丸括弧で囲んで、コンマで切り離さなければなりません。
値の一覧の順番は、型要素の順番に対応します。ここでは、a は 12345 が、b は 12 が、c は "Hello" が、設定されます。

動的な文字列は、型定義の中でこのメソッドを使っても、初期化できません。
文字列は、固定長でなければなりません。


Dim 命令文で、型定義を初期化することは、プログラムの実行中に変化しない、型、または値のために、初期値を設定したいときに、役に立ちます。
値が、コンパイル時に知られているので、コンパイラは、実行時に、値をロードするためにサイクルを費やす必要がなくなります。


Union (共用体)

Union (共用体) は、その定義において、Type と同様に見えます。

Union aUnion
    b As Byte
    s As Short
    i As Integer
End Union


これが Type であれば、定義の中で各項目にアクセスできます。
しかし、Union(共用体) では、その時々で、1つの項目にしかアクセスできません。
Union(共用体) の中のすべての項目は、同じメモリ・セグメントを占領します。そして、Union(共用体) のサイズは、最も大きいメンバーのサイズです。

この場合、Union(共用体) は、Integer のサイズ、4/8 byte (32/64bit システムで) を占有します。b 項目は1バイトを占め、s 項目は2バイトを占め、i は4/8バイト(32/64bit システムで)全てを占めます。
各項目は、最初のバイトから開始されるので、s 項目には b 項目が含まれ、i 項目には b 項目と s 項目の両方が含まれます。

Union(共用体) の中の Type
Union(共用体) の中で、型定義を使う好例は、winnt.bi で見られる、Large_Integer 定義です。
Large_Integer データ型は、C 実行時ライブラリの中の、多くの Windows 関数で使われています。
下の短いコードは、Large_Integer 定義を示しています。

Union LARGE_INTEGER
    Type
        LowPart As DWORD
        HighPart As Long
    End Type
    QuadPart As LONGLONG
End Union


Dword データ型は、FreeBASIC Ulong として windef.bi で定義され、Longlong 型は Longint として定義されます。
Long は固定の32ビット整数型です。
型は連続した記憶域を占めるので、HighPart 項目は、メモリ内の LowPart 部分の項目の後に続くことに注意してください。
これはUnion(共用体) なので、型は、QuadPart 項目と同じメモリセ・グメントを占有します。

QuardPart に、大きい整数値を設定するとき、あなたは、また、型項目の値を設定しています。次に、あなたは、型項目を、LowPart と HighPart として抽出できます。
あなたは、逆も、できます。
すなわち、型の、LowPart と HighPart を設定することによって、QuadPart 項目の値を設定できます。

お分かりのように、 Union(共用体) の中で型を使うと、多くの変換コードを当てにせずに、コンポーネント・データ型の個々の値を、簡単に、設定したり、検索したり、できます。
The layout of the memory segments does the conversion for you, providing that the memory segments make sense within the context of the component type.
メモリ・セグメントのレイアウトは、あなたのために変換します。メモリ・セグメントが、コンポーネント型の文脈の中で、意味を提供します。

Large_Integer の場合では、LowPart と HighPart は、適切なコンポーネント値を返すために、定義されました。
Dword と Long 以外の値を使うと、正しい値は、LowPart と HighPart のために返りません。
Union(共用体) の中で型を定義するとき、型定義の中で、Union(共用体) メモリ・セグメントを、正しく 区分していることを、確認する必要があります。

型の中の Union(共用体)
型定義の中の Union(共用体) は、型の中の 1つの項目が、いくつかの値の1つであるときに、データを管理する、効率的な方法です。
この最も一般的な例は、他のプログラミング言語で使われている Variant データ型です。

FreeBasic には、現在、固有の Variant データ型は、ありません。
しかし、拡張 Type 構文を使って、あなたのプログラムに使うための Variant データ型を作成できます。


When using a Union within a type it is common practice to create an id field within the type that indicates what the union contains at any given moment.
型の中で Union(共用体) を使うとき、 いつなんどきでも、Union(共用体) が含むものを示す型の中で、id 項目を作成するのは、一般的な習慣です。
下の短いコードは、この概念を示しています。

'Union field ids
' Union(共用体) 分野イド'

#define vInteger 0
#define vDouble 1

'変数データ項目に、型 def を定義します
Type vType
    vt_id As Integer
    Union
        d As Double
        i As Integer
    End Union
End Type


ここの Union(共用体) 定義は、名前で定義されないので、匿名の Union(共用体) と呼ばれます。
型定義の vt_id 項目は、Union(共用体) の値を示します。
型を初期化するために、下のようにコードを使います。

Dim myVarianti As vType
Dim myVariantd As vType

myVarianti.vt_id = vInteger
myVarianti.i = 300

myVariantd.vt_id = vDouble
myVariantd.d = 356.56


myVarianti は、整数値を含んでいるので、id は、vInteger で設定します。
myVariantd は、ダブルを含んでいるので、id は、vDouble で設定します。
あなたが、vType パラメタを持っているサブルーチンを作るなら、サブルーチンに、整数かダブルか、どちらが渡されたかを究明するために vt_type 項目を調べることができます。

動的な文字列を、Union(共用体) の中で、使用できません。

プログラムの中で Union(共用体) と型の組み合わせを使うと、多くの柔軟性を持つ、カスタム・データ型を設計できます。しかし、データ構造を正しく使っていることを保証するために、注意しなければなりません。
このデータ型を不適当に使うと、見つけにくいバグを引き起こすことがあります。
しかし、利益は、危険を上回ります。一度マスタすれば、強力なプログラミング・ツールです。

いっしょに学ぼう に戻る

最後、Sancho3によるレビュー(2018年2月06日)

←リンク元に戻る プログラム開発関連に戻る

ページ歴史:2021-06-06 01:08:41
日本語翻訳:WATANABE Makoto、原文著作者:WikiRick(Rick Clark aka rdc)

ホームページのトップに戻る

表示-非営利-継承