2006 年 5 月 6 日 23 時 3 分

ArrayList: 可変配列ラッパ


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


VBA の配列は少し癖があり、関数の戻り値として使用すると、
値のコピーが発生してしまうため、
参照型として利用できる配列が欲しい。

昨日定義したインタフェースには、
ArrayList というクラスが戻り値として存在した。
これは可変配列の参照型ラッパの機能を持つクラスである。

今日はこれを作ってみよう。

まず、必要なメソッドを洗い出す。

・要素の取得・設定: (インデクサ)
・要素数の取得: Count
・内部容量の取得: Capacity
・内部容量の拡大: EnsureCapacity()
・要素の追加: Add()
・要素の挿入: Insert()
・要素の削除: RemoveAt()
・要素の全削除: Clear()

続いてフィールド

・内部配列: m_vValues
・容量: m_lCount

こんなものかな。

そうそう、名前付け規則について書いていなかった。

メソッド名やクラス名は PascalCase が基本である。
VBA は大文字小文字を区別しないので、
引数やローカル変数も自動的に PascalCase となってしまう。
どこかに camelCase を混ぜてしまうと、
同じ名前のメソッド名やクラス名に影響してしまうのだ。

ただ、プログラム利用者から見える引数は仕方ないとしても、
ローカル変数も PascalCase なのは気に入らない。

そこで、ローカル変数やフィールドには、
1 文字の小文字プリフィックスをつけて camel っぽくする。
また、どうせつけることになるならば、固定の文字でなく、
ハンガリアン記法のように型を表す文字とする。

フィールドには、さらに m_ を頭につける。
VBA では、識別子をアンダースコアから始められないためだ。
Me.~ という記法もあるが、面倒なのでこうする。

そして例外的に、アルファベット 1 文字の変数は許可する。
ループカウンタや一時変数、x や y などの引数に使用する。


よし、実装に入ろう。まずは、フィールドだ。

    Private Const DEFAULT_CAPACITY As Long = 16

    Private m_vValues() As Variant
    Private m_lCount    As Long

内部領域には動的配列を利用する。
型は、Java や C# の Object に対応する Variant だ。
また、現在いくつの要素があるか
記憶させておくための変数が m_lCount だ。

DEFAULT_CAPACITY は、既定の容量である。
値を追加するたびに動的に容量を拡大する必要があるので、
既定で 16 要素分確保しておくことにしよう。

次に、初期化コードを書く。

VBA のクラスモジュールは、COM 基盤に影響されており、
コンストラクタに引数を定義できない。

COM 的には、オブジェクト生成と初期化は別であり、
外部から初期化の方法を指定する必要があるなら、
そういうメソッドを定義して呼び出すこととなるのだ。
まあ、今回は必要ないが今後は考えていく必要があるな。

    Private Sub Class_Initialize()
        m_lCount = 0
        ReDim m_vValues(0 To DEFAULT_CAPACITY - 1) As Variant
    End Sub

ReDim で配列を動的に割り当てる。
俺は、ベースや型を明記する癖があるので、
上記は少し冗長ではある。

配列は、基本的に 0 ベースで作成することにしよう。
他の言語に慣れているせいもあるが、
0 ベースの方が計算しやすいことが多いからだ。

後は、簡単なものから実装していこう。

    Public Property Get Count() As Long
        Count = m_lCount
    End Property

VBA には return 文がない。Return はあるが違う意味だ。
メソッドの戻り値は、メソッド名の変数に代入して指定する。

    Public Property Get Capacity() As Long
        Capacity = UBound(m_vValues) + 1
    End Property

UBound は、配列要素インデックスの上限を得る関数だ。
0 ベースにおいて上限は、要素数ではないため、
1 を加算することで、内部配列の容量となる。
完全にするなら、UBound(~) - LBound(~) + 1 か。

    Public Sub Clear()
        Dim i As Long
        For i = 0 To m_lCount - 1
            m_vValues(i) = Empty
        Next
        m_lCount = 0
    End Sub

全クリアは簡単だ。m_lCount = 0 だけでもよさそうだが、
配列要素をクリアしておかないと、
オブジェクトが解放されないので忘れずに。

さて、後は結構ややこしいので、明日にしよう



Copyright (c) 1994-2007 Project Loafer. All rights reserved.