2006 年 5 月 7 日 22 時 16 分

ArrayList #2: 値型と参照型


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


昨日の続き。要素の追加をやろう。

ArrayList は可変長であるため、
要素数を決め打つことはできず、
必要に応じて拡張していく必要がある。

そのため要素の追加でやることは 2 つ。
・必要に応じて容量を拡大する
・内部配列に要素を格納する

このうち容量を拡大するのは、専用のメソッド
EnsureCapacity() で行うのでそれを先に作成する

    Public Sub EnsureCapacity(ByVal MinimumCapacity As Long)
        Dim lNew As Long
        If Capacity >= MinimumCapacity Then Exit Sub
        lNew = Capacity * 2
        If lNew = 0 Then lNew = DEFAULT_CAPACITY
        If lNew < MinimumCapacity Then lNew = MinimumCapacity
        ReDim Preserve m_vValues(0 To lNew - 1) As Variant
    End Sub

容量の拡大のタイミングの制御は難しいので、今回は適当で。
VBA の配列は、ReDim Preserve ステートメントを使うと、
内容を維持したまま容量を拡大することができる。

要素の追加は、内部で EnsureCapacity を呼び出せば、
要素を格納する処理だけを考えれば済む。

    Public Function Add(ByVal Value As Variant) As Long
        Call EnsureCapacity(m_lCount + 1)
        If IsObject(Value) Then
            Set m_vValues(m_lCount) = Value
        Else
            m_vValues(m_lCount) = Value
        End If
        Add = m_lCount
        m_lCount = m_lCount + 1
    End Function

VBA の厄介な所は、値型と参照型の扱いが異なることだ。
特に代入の時にそれが表面化する。
値型の代入は Let ステートメント、
参照型の代入式は、Set ステートメントとなる。

Variant 型は内部に何でも格納できるが、
値を代入する際は、この差を考える必要がある。
なお、Let は省略できるので、
値型の代入は、構文上は他の言語の代入と変わらない。

参照型かどうか確認するためには、
IsObject 関数を使えばよい。


次は、要素の挿入・削除を考えてみる。
途中に値を挿入した場合、以降の要素をずらす必要がある。
削除の場合も同様に必要だ。
つまり、要素を 1 つずらすヘルパメソッドが要る。

ヘルパメソッドは外部から呼ばれることはないため、
シグネチャ(引数や戻り値の規定)は
勝手な都合で作成できる。
ここでは、First, Last, Direction としておこう。
First, Last は名前通り、Direction は、1 か -1 でいい。

    Private Sub ShiftElements(ByVal First As Long, ByVal Last As Long, ByVal Direction As Long)
        Dim i As Long
        For i = First To Last Step -Direction
            If IsObject(m_vValues(i)) Then
                Set m_vValues(i + Direction) = m_vValues(i)
            Else
                m_vValues(i + Direction) = m_vValues(i)
            End If
        Next
    End Sub

これを利用すれば、挿入や削除も楽に実装できる。

    Public Sub Insert(ByVal Index As Long, ByVal Value As Variant)
        Dim i As Long
        If Index < 0 Or Index > m_lCount Then
            Call Err.Raise(9)
        End If
        Call EnsureCapacity(m_lCount + 1)
        If Index < m_lCount Then
            Call ShiftElements(m_lCount - 1, Index, 1)
        End If
        If IsObject(Value) Then
            Set m_vValues(Index) = Value
        Else
            m_vValues(Index) = Value
        End If
        m_lCount = m_lCount + 1
    End Sub

値を挿入する場合インデックスの範囲に注意する必要がある。
必要以上に配列を拡張するのを避けるため、
内部配列は実際の要素数より大きく割り当てている。

そのため、内部配列に代入する際には、
内部配列のエラーに頼ることができないため、
プログラム側で範囲チェックを行なう必要がある。

配列の境界のエラー番号は、VBA で 9 と定められている。
定数として定められていないので気持ち悪いが、
Err.Raise(9) で強制的にエラーを発生させることができる。
VBERROR_INDEX_OUT_OF_RANGE など定数化してもいいかも。

要素の削除も基本的には同じだ。

    Public Sub RemoveAt(ByVal Index As Long)
        Dim i As Long
        If Index < 0 Or Index >= m_lCount Then
            Call Err.Raise(9)
        End If
        If Index < m_lCount - 1 Then
            Call ShiftElements(Index + 1, m_lCount - 1, -1)
        End If
        m_lCount = m_lCount - 1
        m_vValues(m_lCount) = Empty
    End Sub

削除した要素には、Empty を代入しておく。
参照型の参照が残らないようにだ。
Empty は Variant 専用の特殊型であり「空」を意味する。
Empty は値型のキーワードだ。



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