ExcelVBA 　文法

::: note warn
自分用のメモなので、形は整ってないです。
:::
### Stop と Debug.Assert 　どちらもコードの実行を強制停止する
```
Sub aaa()

Dim long1 As Long
Dim i As Long

long1 = 0
For i = 0 To 100
  long1 = long1 + 1
  Debug.Print long1
  If long1 > 5 Then
    Stop
    ' コードの実行をStopで強制停止
    'ブレークポイントで止まったような形になり、Exit Sub や End のようになるわけではない
    ' F5やF8で進めることはできるが、この部分に来るたびに毎回止まる
  End If
Next i

End Sub


Sub bbb()

Dim long1 As Long
Dim i As Long

long1 = 0
For i = 0 To 100
  long1 = long1 + 1
  Debug.Print long1
  Debug.Assert long1 < 5
  'Debug.Assert で、条件を満たさなくなった場合に強制停止
  '事実上Stopと同じ機能だが、こちらは停止時に警告音が鳴る
Next i

End Sub
```
### Goto、GoSub　　コードの特定の位置へジャンプする機能だが、エラー処理･最終処理以外はあまり推奨されない
```
Sub eee()
' GoTo文

  Dim lng1 As Long
  lng1 = InputBox("数値を入力してください")
  
  If lng1 < 10 Then
    GoTo Point1  'Point1 へジャンプ
  End If
  
  MsgBox lng1
  
  Exit Sub  'これを忘れないように。これが無いと、以下のコードまで実行される
  
Point1:  'ジャンプで来る位置
  lng1 = lng1 + 10
  MsgBox lng1
' このままプロシージャを抜ける

End Sub



Sub fff()
' GoSub文  GoSubとReturnをセットで使う

  Dim lng1 As Long
  lng1 = InputBox("数値を入力してください")
  
  If lng1 < 10 Then
    GoSub Point2  'Point2 へジャンプ
  End If
  
  MsgBox lng1
  
  Exit Sub  'これを忘れないように。これが無いと、以下のコードまで実行される
  
Point2:  'ジャンプで来る位置
  lng1 = lng1 + 10
  Return  ' Returnで、元の位置へ戻る

End Sub
```
### Enum　　Long型の定数値のみを複数格納する列挙体
```
Private Enum UserInfo  'Enumの宣言。Long型の定数のみ登録できる
  名前 = 1
  住所 = 2
  電話番号 = 3
  性別 = 4
  年齢 = 5
End Enum

Private Enum MyBoolean
  Yes = 1
  No = 0
End Enum

Private Enum Settings
  inputCellRow = 2
  inputCellColumn = 1
  outputCellRow = 5
  outputCellColumn = 2
End Enum

Sub aaa()

Dim arrUsers(1 To 5) As String 'インデックス1～5の配列を宣言

arrUsers(1) = "山田"
arrUsers(2) = "長崎県"
arrUsers(3) = "0120-444-888"
arrUsers(4) = "男"
arrUsers(5) = "45"

Debug.Print arrUsers(UserInfo.名前)
'Enumを使って、何番目の要素であるかを求める。UserInfo. の時点で候補が表示されるので、わかりやすい
Debug.Print arrUsers(UserInfo.電話番号)


Dim xValue As String

xValue = CStr(Cells(Settings.inputCellRow, Settings.inputCellColumn).Value)
Cells(Settings.outputCellRow, Settings.outputCellColumn).Value = xValue
'セルの行番号・列番号を定義したEnumを利用して、セルを特定する

End Sub
```
### Collection　　同じデータ型の要素を複数まとめたもの
```
Sub aaa()

Dim col1 As New Collection
'Collectionオブジェクトの宣言
'object.Add item, key, before, after　の構文

With col1
  .Add Item:="Name", Key:="key1"
  '要素名"Name"　キー名"key1"で要素を追加
  
  .Add "Address", "key2"
  'この表記でもいい　要素名"Address"　キー名"key2"で要素を追加
  
  .Add Item:="Age", Key:="key3", Before:="key1"
  ' Before:="key1" で、key1の要素の前に要素を追加
End With


Debug.Print col1.Count    '要素数を取得
Debug.Print col1(2)       'インデックス番号でItemを取得。インデックスは1から始まる
Debug.Print col1("key1")  ' キー名でItemを取得
Debug.Print col1.Item(1)  ' 1番目のItemを取得

Debug.Print "-----------------------"

Dim i As Long
For i = 1 To col1.Count
  Debug.Print col1(i)
Next i
'多数の要素を持つCollectionに対してインデックス番号指定すると、処理が遅くなるらしい

Debug.Print "-----------------------"

Dim var1 As Variant
For Each var1 In col1
  Debug.Print var1   ' これもItemを取得できる
Next var1

Debug.Print "-----------------------"

With col1
  .Remove ("key2")  'key2の要素を削除
  .Add "ABCD"       'キーの指定無しで要素を追加
End With

For Each var1 In col1
  Debug.Print var1
Next var1

End Sub



Sub bbb()

Dim rangeCol As New Collection
Dim sheetCol As New Collection
Dim var1 As Variant
Dim var2 As Variant


rangeCol.Add Range(Cells(1, 1), Cells(10, 1)), "range1"
'Rangeオブジェクトを要素として追加できる
rangeCol.Add Range(Cells(1, 3), Cells(10, 3)), "range2"

'Debug.Print rangeCol(1)  ' これはエラー。Rangeオブジェクトなので

'Rangeの各セルの値を取得
For Each var1 In rangeCol
  For Each var2 In var1
    Debug.Print var2.Value
  Next var2
  Debug.Print "-----------------------"
Next var1


rangeCol.Add "Range3", "range3"  '文字列型の要素を追加。このコードはエラーにならない
Debug.Print rangeCol("range3")  ' これはエラーにならない

'※実際には、異なる型のデータを混在させるべきではない


sheetCol.Add Worksheets(1)  'ワークシートオブジェクトをCollectionに追加
Debug.Print sheetCol(1).name 'ワークシート名を取得できる


End Sub
```
### デフォルト値を持つ引数がある、可変長の引数があるFunctionの例　　プロシージャでも同様
```
Sub aaa()

'*********  デフォルト値を持つ引数があるFunction　　*****

  Debug.Print func1(1000, 0.15)  '第2引数も指定   850
  Debug.Print func1(1000)        '第2引数は省略   800

  Debug.Print func2(1000, 0.3)   '第2引数も指定   700
  Debug.Print func2(1000)        '第2引数は省略   800
  
  Debug.Print "-----------------"
  
'*********  引数の数が不定のFunction　　*****************

  Debug.Print func3(10, 20, 30)  '60
  Debug.Print func3(-100)        '-100
  Debug.Print func3()            '0

End Sub


Function func1(ByVal price As Double, Optional ByVal discount As Double = 0.2) As Double
'Optionalをつけた引数は、デフォルト値を使用できる。discountのデフォルト値は0.2
'Optionalをつけた引数以降は、全てOptional付きでなくてはならない

  func1 = price * (1 - discount)

End Function


Function func2(ByVal price As Double, Optional ByVal discount As Variant) As Double
'デフォルト値を指定しない例

  If IsMissing(discount) Then
  'IsMissingで、引数の指定が省略されたかを判定できる
  '判定できるのはVariant型の引数のみ。他の型は常にTrueになってしまう
    discount = 0.2
  End If
  
  func2 = price * (1 - discount)

End Function


Function func3(ParamArray values()) As Long
' "ParamArray" キーワードで、可変長の引数である事を宣言。ByRefとかと同じ役割
'可変長の引数は、引数の最後に1つだけしか指定できない。Variant型のみ。Longとかにするとエラーなので型は省略で
'可変長の引数は配列扱いなので、参照渡しになる。ByRefとかは付けられない

  Dim total As Long
  Dim i As Long
  
  total = 0
  For i = LBound(values) To UBound(values)
    total = total + values(i)
  Next i
  
  func3 = total
End Function
```
### データ型
```
Dim int1 As Integer   ' 整数型　 -32,768 ～ 32,767
Dim long1 As Long     ' 長整数型  -2,147,483,648 ～ 2,147,483,647
Dim single1 As Single   ' 単精度浮動小数点数型　-3.402823E38 ～ -1.401298E-45(負の値)　1.401298E-45 ～ 3.402823E38(正の値)
Dim double1 As Double
' 倍精度浮動小数点数型  -1.79769313486232E308 ～ -4.94065645841247E-324(負の値)  4.94065645841247E-324 ～ 1.79769313486232E308(正の値)
Dim cur1 As Currency    ' 通貨型  -922,337,203,685,477.5808 ～ 922,337,203,685,477.00
Dim str1 As String    ' 文字列型　最大約20億文字まで
Dim date1 As Date    ' 日付型       西暦100 年1月1日～西暦9999年12月31日までの日付と時刻
Dim bol1 As Boolean  ' ブール型  True or False
Dim obj1 As Object   ' オブジェクト型
Dim var1 As Variant  ' バリアント型

' ※他にも多数あるが、主なものは上記あたりになる
```
### 演算子
```
MsgBox 1 + 1     ' 加算
MsgBox 1 - 1     ' 減算
MsgBox 2 * 100   ' 乗算
MsgBox 100 / 3   ' 除算
MsgBox 10 \ 3   ' 除算の整数部分を取得(この場合は3)
MsgBox 10 Mod 3   ' 除算の余りを取得(この場合は1)
MsgBox 10 ^ 3   ' べき乗

MsgBox "AA" & "BB"  ' 文字列の連結

MsgBox 1 = 1   ' 等しい
MsgBox 1 <> 1   ' 等しくない
MsgBox 1 < 1   ' 不等号
MsgBox 1 <= 1   ' 不等号
MsgBox 1 > 1   ' 不等号
MsgBox 1 >= 1   ' 不等号

MsgBox "abc" Like "a?c"   ' ?は任意の1文字
MsgBox "abc" Like "a*"   ' *は任意の数の文字
MsgBox "12345" Like "12##"   ' #は任意の半角数字1文字
MsgBox "A" Like "[A-F]"   ' A～Fまでのいずれか
MsgBox "A" Like "[!A-F]"   ' A～Fまでのいずれかではない

Dim range1 As Range
Dim range2 As Range

Set range1 = Range("A1")
Set range2 = Range("A2")

MsgBox range1 Is range2    ' オブジェクトが一致するか

Dim range3 As Range
MsgBox range3 Is Nothing    ' オブジェクトが空か

Dim true1 As Boolean
Dim true2 As Boolean
Dim false1 As Boolean
Dim false2 As Boolean
true1 = True
true2 = True
false1 = False
false2 = False

MsgBox Not true1  ' 否定演算子

' 論理積
MsgBox true1 And true2   ' true
MsgBox true1 And false1   ' false

' 論理和
MsgBox true1 Or false1   ' true
MsgBox false1 Or false2   ' false

' 排他論理和
MsgBox true1 Xor false1   ' true
MsgBox false2 Xor true2   ' true
MsgBox true1 Xor true2    ' false
MsgBox false1 Xor false2   ' false

' Eqv 論理等価   Imp 論理包含 などもあるが、使い道無さそう
```
### Withステートメント
```
With ActiveSheet
    With .Range("A1")  ' Withのネスト
        .Value = "ABC"
        .Interior.ColorIndex = 5
    End With
End With
```
### IF文
```
Dim long1  As Long
long1 = 50

If long1 >= 100 Then
  MsgBox "100以上です"
Else
  MsgBox "100未満です"
End If

long1 = 250
If long1 >= 1000 Then
  MsgBox "1000以上です"
ElseIf long1 >= 100 Then
  MsgBox "100以上です"
Else
  MsgBox "100未満です"
End If


Rows(1).Hidden = True
If Rows(1).Hidden Then   ' =true の省略
  Rows(1).Hidden = False
End If
If Not Rows(1).Hidden Then   ' Notで =false の省略
  Rows(1).Hidden = True
End If
```
### Select Case 文
```
Dim str1 As String
str1 = "A"

Select Case str1
Case "A"
    MsgBox "Aです"
Case "B"
    MsgBox "Bです"
Case Else
    MsgBox "それ以外です"
End Select


Dim long1 As Long
long1 = 100

Select Case long1
Case Is <= 10
    MsgBox "10以下です"
Case Is <= 100
    MsgBox "11以上100以下です"
Case Else
    MsgBox "101以上です"
End Select

long1 = 30
Select Case long1
Case 10, 20
    MsgBox "10または20です"
Case 30, 40
    MsgBox "30または40です"
Case Else
    MsgBox "それ以外です"
End Select

long1 = 222
Select Case long1
Case 0 To 150
    MsgBox "0～150の範囲です"
Case 151 To 300
    MsgBox "151～300の範囲です"
Case Else
    MsgBox "それ以外の範囲です"
End Select


long1 = 50
If long1 = 50 Then MsgBox "50です": MsgBox "再び50です"
'End If は無くてもいい　: で複数の文を1行に記述できる

If long1 = 50 Then
MsgBox "aaa": MsgBox "bbb": MsgBox "ccc": Exit Sub
'最後に End とか Exit Sub を置くのにちょうどいいかな
End If
```
### For Next 文
```
Dim i As Long
Dim j As Long

For i = 1 To 10
    MsgBox i
Next i

For i = 1 To 10 Step 2    ' 2づつインクリメント
    MsgBox i
Next i

For i = 10 To 0 Step -2    ' 2づつデクリメント
    MsgBox i
Next i

' For Nextのネスト
For i = 1 To 5
    For j = 1 To 3
        MsgBox i * j
    Next j
Next i

For i = 1 To 10
    If i > 5 Then
        Exit For   ' ループを抜ける
    End If
    MsgBox i
Next i
```
### For Each 文
```
Dim ws As Worksheet

For Each ws In Worksheets  ' 全てのワークシートをループ処理
    MsgBox ws.Name
    If ws Is Worksheets(5) Then
        Exit For
    End If
Next ws

Dim var1 As Variant

For Each var1 In Array(1, 2, 3, 4, 5)   ' ループ用変数にはバリアント型がいいかも
    MsgBox var1
Next var1

For Each var1 In Range("A1:B10")   ' A1:B10の範囲を全てループ
    MsgBox var1.Value
Next var1
```
### Do loop 文
```
Dim long1 As Long
long1 = 0

Do While long1 < 10   ' ～の間は　の条件
    MsgBox long1
    long1 = long1 + 1
Loop

long1 = 5
Do Until long1 >= 10   ' ～まで　の条件
    MsgBox long1
    long1 = long1 + 1
Loop

long1 = 0
Do While long1 < 100
    If long1 > 10 Then
        MsgBox long1
        Exit Do   ' ループを抜ける
    End If
    long1 = long1 + 1
Loop

long1 = 100
Do
    MsgBox long1
    long1 = long1 + 1
Loop While long1 < 50   ' 条件分岐を後に置くので、必ず1回はループ内の処理を実行

long1 = 102
Do
    MsgBox long1
    long1 = long1 + 1
Loop Until long1 > 50
```
### 他のプロシージャを呼びだす
```
**************   他のプロシージャを呼びだす   *******************

Sub aaa()

    Call bbb    ' 引数無しでプロシージャ呼び出し
    
    Call ccc(1200, "SSS")   ' 引数付きでプロシージャ呼び出し
    
    Application.Run "'C:\まとめ総合\ExcelVBA\DDD\ddd.xlsm'!ddd"
    ' C:\まとめ総合\ExcelVBA\DDD\ddd.xlsm のddd()プロシージャを呼び出し
    ' ブックのパス・ブック名は''で囲む
    
End Sub
 

Sub bbb()

    MsgBox "bbb()が呼ばれました"
    
End Sub


Sub ccc(long1 As Long, str1 As String)

    MsgBox "引数1：" & long1 & vbCrLf & "引数2：" & str1 & vbCrLf & "でccc()が呼ばれました"
    
End Sub


*****  C:\まとめ総合\ExcelVBA\DDD\ddd.xlsm のddd()    *************
Sub ddd()

    MsgBox "ddd.xlsm の ddd()が呼ばれました"
    
End Sub
```
### 値渡しと参照渡し　プロシージャ側ではByVal・ByRefを明示しておくこと
```
Sub aaa()

Dim long1 As Long

' ※Call と () の指定の仕方で、参照渡し、値渡しが変わってくる

long1 = 100
bbb long1            ' 参照渡しになる(参照渡し、値渡しを明示していないプロシージャの場合)
Debug.Print long1    ' 111　参照渡しになるので

long1 = 100
ccc (long1)          ' 値渡しになる(参照渡し、値渡しを明示していないプロシージャの場合)
Debug.Print long1    ' 100　値渡しになるので

long1 = 100
Call bbb(long1)      ' 参照渡しになる(参照渡し、値渡しを明示していないプロシージャの場合)
Debug.Print long1    ' 111　参照渡しになるので

long1 = 100
Call ccc((long1))    ' 値渡しになる(参照渡し、値渡しを明示していないプロシージャの場合)
Debug.Print long1    ' 100　値渡しになるので

Debug.Print "--------------------------------------------"

long1 = 100
ddd long1            ' 参照渡しの形で、値渡しを明示しているプロシージャを呼び出し
Debug.Print long1    ' 100　値渡しになる

long1 = 100
eee (long1)          ' 値渡しの形で、参照渡しを明示しているプロシージャを呼び出し
Debug.Print long1    ' 100  値渡しになるらしい

long1 = 100
Call eee(long1)      ' 参照渡しの形で、参照渡しを明示しているプロシージャを呼び出し
Debug.Print long1    ' 444　参照渡しになる

long1 = 100
Call ddd((long1))    ' 値渡しの形で、値渡しを明示しているプロシージャを呼び出し
Debug.Print long1    ' 100　値渡しになる


'おそらく、プロシージャ側ではByVal・ByRefを明示しておき、
'呼び出し側では参照渡しの形 (Call eee(long1)  のような形)にするのがベスト、と言うか必須
'いい加減な形にすると、どちらになるのかはっきりしなくなる


Debug.Print "--------------------------------------------"

'Functionの呼び出し

long1 = 100
Call func01(long1)   ' 参照渡しの形で、参照渡しを明示しているFunctionを呼び出し
Debug.Print long1    ' 555　参照渡しになる

long1 = 100
Call func02(long1)   ' 参照渡しの形で、値渡しを明示しているFunctionを呼び出し
Debug.Print long1    ' 100　値渡しになる


Dim strResult As String

long1 = 100
strResult = func01(long1)
' この形でのFunctionを呼び出しは、参照渡しになる 参照渡しを明示しているFunctionを呼び出し
Debug.Print long1          ' 555　参照渡しになる
Debug.Print strResult      ' FUNC01

long1 = 100
strResult = func02(long1)  ' 値渡しを明示しているFunctionを呼び出し
Debug.Print long1          ' 100　値渡しになる
Debug.Print strResult      ' FUNC02


End Sub



Sub bbb(long1 As Long)  '参照渡し、値渡しを明示していない。これはデフォルトで参照渡しで引数を受け取る

  long1 = 111

End Sub


Sub ccc(long1 As Long)  '参照渡し、値渡しを明示していない。これはデフォルトで参照渡しで引数を受け取る
  long1 = 222

End Sub


Sub ddd(ByVal long1 As Long)  '値渡しを明示

  long1 = 333

End Sub

Sub eee(ByRef long1 As Long)  '参照渡しを明示

  long1 = 444

End Sub


Function func01(ByRef long1 As Long) As String  '参照渡しを明示したFunction

  long1 = 555
  func01 = "FUNC01"

End Function


Function func02(ByVal long1 As Long) As String  '値渡しを明示したFunction

  long1 = 666
  func02 = "FUNC02"

End Function
```
### Exit Sub とEnd
```
Sub aaa()

    Call bbb(20)
    
    MsgBox "aaa()の完了"

End Sub

Sub bbb(long1 As Long)

    If long1 > 100 Then
        Call ccc
    ElseIf long1 > 30 Then
        Exit Sub   ' このプロシージャを抜ける
    Else
        End  ' 全てのプロシージャの処理を終了する。全変数の値も初期化されるので注意
    End If

End Sub

Sub ccc()

    MsgBox "ccc()が呼ばれました"
    
End Sub
```
### Functionプロシージャ
```
Sub aaa()

    Dim result As String
    
    result = func1(55)   ' Functionプロシージャ呼び出し
    MsgBox result
    
    result = func2(20)
    MsgBox result
    
    MsgBox func3(100, 200)
    
    MsgBox func4(0)
    MsgBox func4(255)

End Sub


Function func1(long1 As Long) As String   ' String型の戻り値を返すFunctionプロシージャ

    Select Case long1
    Case Is >= 80
        func1 = "優"   ' このFunctionプロシージャの戻り値を設定(プロシージャ名をそのまま指定)
    Case Is >= 60
        func1 = "良"
    Case Is >= 40
        func1 = "可"
    Case Else
        func1 = "不可"
    End Select

End Function


Function func2(long2 As Long)  ' 戻り値の型は省略してもいい。省略した場合は、戻り値の型はバリアント型になる

    func2 = long2 * 30

End Function


Function func3(ByVal int1 As Integer, ByRef int2 As Integer) As Integer
'ByValで値渡し、ByRefで参照渡し。省略すると参照渡しになる

    func3 = int1 + int2
    
End Function


Function func4(int1 As Integer) As Integer

    If int1 < 1 Then
        func4 = -100
        Exit Function  'Functionを抜ける
    End If
    
    func4 = int1

End Function
```
### 配列(静的配列)
```
Dim array1(2) As String
' 要素3の一次元配列。Javaと違ってarray1(2)とすると要素3になることに注意
' ※Option Base 1 を宣言すれば、array1(3)とすると要素3になるが、他の影響もあるので良くないかも

array1(0) = "AAA"
array1(1) = "BBB"
array1(2) = "CCC"
' データを格納

MsgBox array1(2)  ' データを取得


Erase array1   ' 配列の初期化

MsgBox array1(2)  ' 初期化されているので、データは空文字になる


Dim array2(2, 1) As Long  ' 二次元配列
array2(0, 0) = 10
array2(0, 1) = 20
array2(1, 0) = 100
array2(1, 1) = 200
array2(2, 0) = 1000
array2(2, 1) = 2000

Dim var1 As Variant
For Each var1 In array2
  Debug.Print var1
  ' For Each で全取得する場合、取得順は1次元の要素からになる(10 100 1000 の順番)
Next var1


Dim var2 As Variant
Dim var3 As Variant
var2 = Array(10, 20, 30, 40, 50)
' バリアント変数を配列として使用する場合は、Array()で一括のデータ格納が可能

For Each var3 In var2
  Debug.Print var3
Next var3


Dim array3(1 To 5) As String
'インデックスが1から始まる、5要素の配列を宣言　これが一番分かりやすいかも
Dim array4(1 To 3, 1 To 5) As String
'二次元配列も可能
```
### 動的配列　　要素数を変更できる配列
```
Dim array1() As Long   ' 動的配列

'MsgBox UBound(array1)  ' この時点ではまだ要素が無いので、このコードはエラーになる

ReDim array1(4)   ' 要素数を5に設定
array1(0) = 10
array1(1) = 20
array1(2) = 30
array1(3) = 40
array1(4) = 50

ReDim array1(5)  ' 要素数を変更すると、データは初期化されるので注意
MsgBox array1(4)  ' 0と表示

array1(0) = 100
array1(1) = 200
array1(2) = 300
array1(3) = 400
array1(4) = 500
array1(5) = 600
ReDim Preserve array1(6)   ' ReDim Preserve を使って要素数を変更すると、データは初期化されない
MsgBox array1(5)  ' 600と表示


array1(6) = 700
Dim i As Long

' 動的配列の全データを取得する場合は、LBoundとUBoundを使うのが簡単
For i = LBound(array1) To UBound(array1)
  Debug.Print array1(i)
Next i


Dim array2() As Long
ReDim array2(2, 3)   ' 二次元の動的配列として要素を確保

ReDim Preserve array2(2, 5)  ' ReDim Preserveで二次元の動的配列の要素数を変更できるのは、最後の次元のみ
'ReDim Preserve array2(3, 5)  ' これは不可ということ


'各次元の上限値を取得する
Dim array3() As Long
ReDim Preserve array3(1 To 10, 1 To 5)
'10×5の2次元配列にする。インデックスは1から始まる形に

Debug.Print UBound(array3, 1)   '1次元の要素数を取得  10
Debug.Print UBound(array3, 2)   '2次元の要素数を取得  5
```
### 配列の関数　Ubound　Filterなど　Filterは便利　　Eraseも動作の再確認を
```
Sub aaaa()

Dim arr1(0 To 4) As Long
Dim arr2(1 To 3, 2 To 5) As String

Debug.Print LBound(arr1)     '配列の最小のインデックス値を取得   0
Debug.Print LBound(arr2, 1)  '2次元配列の、1次元目の最小のインデックス値を取得   1
Debug.Print LBound(arr2, 2)  '2次元配列の、2次元目の最小のインデックス値を取得   2

Debug.Print UBound(arr1)     '配列の最大のインデックス値を取得   4
Debug.Print UBound(arr2, 1)  '2次元配列の、1次元目の最大のインデックス値を取得   3
Debug.Print UBound(arr2, 2)  '2次元配列の、2次元目の最大のインデックス値を取得   5

Debug.Print "-------------------"

Dim var1 As Variant
var1 = Array(10, 20, 30)  'Array関数で、配列が格納されたVariant型の値を返す
Debug.Print var1(0)       '10
Debug.Print var1(2)       '30
Debug.Print LBound(var1)  '0
Debug.Print UBound(var1)  '2
Debug.Print VarType(var1(0))  ' 2　整数値　配列の要素はVariant型ではない

'arr1 = Array(10, 20, 30)  これはエラーなので注意

Debug.Print "-------------------"

Debug.Print IsArray(arr1)  'IsArray関数で、配列であるかを判定  Booleanで返る　　True
Debug.Print IsArray(var1)  'True  Array関数で返されたバリアント型は配列扱い
Debug.Print IsArray(100)   'False

Debug.Print "-------------------"

Dim var2 As Variant
Dim var3 As Variant
var2 = Array(100, 2000, 30000)
var3 = Array("aaa", "bbb", "ccc", "ddd")

Debug.Print Join(var2, "-")   ' "100-2000-30000"
'Join関数で、配列の各要素を指定の文字で連結し、文字列として返す　第二引数が連結文字

Debug.Print Join(var3)      ' "aaa bbb ccc ddd"  連結文字を省略すると、半角スペースで連結
Debug.Print Join(var3, "")  ' "aaabbbcccddd"

Debug.Print "-------------------"


Dim var4 As Variant
Dim var5 As Variant
Dim var6 As Variant
var4 = Array(1, 2, 3)
var5 = Array("aab", "ABB", "ccc", "ddd", "cde", "CDE")

var6 = Filter(var5, "a", , 1)
' var5の要素のうち、"a" を含む要素のみを取り出してvar6に配列として格納
'第4引数を1にすると、テキストモードで比較する

Call OutputArray(var6)  ' (aab,ABB)    OutputArrayは自作の配列要素を出力するSub

var6 = Filter(var5, "a", , 0)  '第4引数を0にすると、バイナリモードで比較する
Call OutputArray(var6)   ' (aab)

var6 = Filter(var5, "", , 1)  ' 検索文字列を空文字にする
Call OutputArray(var6)  ' (aab,ABB,ccc,ddd,cde,CDE)   検索文字列を空文字にすると、絞込みされない


var6 = Filter(var4, "1", , 1)
Call OutputArray(var6)  ' (1)  要素が数値型でも可能らしい
Debug.Print VarType(var6(0))  ' 8 　文字列型に変換されている


var6 = Filter(var5, "a", False, 1)
' 第3引数をFalseにすると、"a"を含まない要素を取り出す
Call OutputArray(var6)    '(ccc,ddd,cde,CDE)


Dim arr3() As String
arr3 = Filter(var5, "c", , 1)  ' 動的配列に格納することも可能
Call OutputArray(arr3)  ' (ccc,cde,CDE)

Debug.Print "-------------------"


' Erase 要素を初期化･メモリ領域を開放する
Dim NumArray(10) As Long
Dim StrVarArray(10) As String
Dim StrFixArray(10) As String * 10
Dim VarArray(10) As Variant
Dim ObjArray(10) As Object
Erase NumArray 'すべて0に
Erase StrVarArray 'すべて長さ0の文字列("")に
Erase StrFixArray 'すべて0に
Erase VarArray 'すべてEmpty値に
Erase ObjArray 'すべてNothingに

Dim DynamicArray() As Long
ReDim DynamicArray(10)
Erase DynamicArray 'メモリを解放

Stop  'ここで各配列の内容を確認するといい

End Sub


Sub OutputArray(ByRef var1 As Variant)  ' 配列の内容を出力する
  
  Dim i As Long
  Dim str1 As String
  
  str1 = "("
  For i = LBound(var1) To UBound(var1)
    str1 = str1 & var1(i) & ","
  Next i
  str1 = Mid$(str1, 1, Len(str1) - 1) & ")"
  Debug.Print str1

End Sub
```
### Type ユーザ定義型変数(複数の型の変数の集合)
```
Type PersonalData   ' ユーザ定義型変数。宣言部でのみ定義可能
    Name As String
    MaxResult As Long
    MinResult As Integer
    Average As Double
    IsUnderForty As Boolean
End Type

Sub aaa()

    Dim data1 As PersonalData
    Dim data2 As PersonalData

    data1.Name = "井上健太"
    data1.MaxResult = 20000
    data1.MinResult = 2500
    data1.Average = 8521.5
    data1.IsUnderForty = False
    
    data2.Name = "森内善治"
    data2.MaxResult = 30000
    data2.MinResult = 6500
    data2.Average = 12556.3
    data2.IsUnderForty = True
    
    Debug.Print data1.Name
    Debug.Print data1.MaxResult
    Debug.Print data1.MinResult
    Debug.Print data1.Average
    Debug.Print data1.IsUnderForty
    
    Debug.Print data2.Name
    Debug.Print data2.MaxResult
    Debug.Print data2.MinResult
    Debug.Print data2.Average
    Debug.Print data2.IsUnderForty
    
    
    ' ※複数のデータ型を大量に扱うにはいいかも
    
End Sub
```
### Declare 　ステートメント　(DLLのプロシージャへの外部参照を宣言する)
```
Option Explicit

Declare Function GetTickCount Lib "kernel32" () As Long
' DLLへの外部参照を宣言

Sub aaa()
    Dim buf As String
    Dim StartTime As Long
    Dim EndTime As Long
    Dim count As Long
    
    StartTime = GetTickCount
    ' GetTickCount で、OS起動時からの経過時間をミリ秒で取得
    
    buf = Dir("C:\Windows\System32\*.*")   ' C:\Windows\System32 フォルダの全ファイルを検索
    
    Do While buf <> ""
        count = count + 1
        buf = Dir()
    Loop
    
    EndTime = GetTickCount
    MsgBox count & "個のファイル" & vbCrLf & EndTime - StartTime & "ミリ秒かかりました"
    
End Sub
```
### 関数・ステートメントで、()の有無による違い　(戻り値を返すかどうかの違い)
```
' ※関数やステートメントに()がある場合は戻り値を返し、無い場合は返さない

MsgBox "OK"   ' これは()が無いので、戻り値を返さない

If MsgBox("続行しますか?", vbYesNo) = vbYes Then   ' これは()があるので、戻り値を返す
    MsgBox "続行します"
Else
    MsgBox "中止します"
End If


Worksheets.Add After:=Worksheets(Sheets.count)   ' これは()が無いので、戻り値を返さない

Worksheets.Add(After:=Worksheets(Sheets.count)).Name = "newSheet"   ' これは()があるので、戻り値を返す
```
### IIf文　IF分の簡易版
```
ワークシート関数のIFに近いが、Trueの場合の式とFalseの場合の式が常に両方評価されることに注意。あと、速度が遅い

Sub aaa()

  Dim i As Long
  Dim num1 As Long
  Dim num2 As Long
  Dim str1 As String
  Dim startTime As Double
  Dim endTime As Double
  
  num1 = 101
  
  str1 = IIf(num1 Mod 2 = 0, "偶数です", "奇数です")
  'IIf文　条件,Trueの場合,Falseの場合
  '条件式の成否に関わらず、True,Falseの両方の式が評価されることに注意
  
  Debug.Print str1


  num1 = 0
  num2 = IIf(100 Mod 2 = 0, 10 * num1, 10 / num1)
  '条件式はTrueとなるが、Falseの式も評価されて0で除算となってしまい、エラー
  
  
  'IIf文は処理速度が遅いらしい
  startTime = Timer
  num1 = 101
  For i = 1 To 1000000
    str1 = IIf(num1 Mod 2 = 0, "偶数です", "奇数です")
  Next i
  endTime = Timer
  Debug.Print "IIf文 :  " & (endTime - startTime)
  
  
  startTime = Timer
  num1 = 101
  For i = 1 To 1000000
    If num1 Mod 2 = 0 Then
      str1 = "偶数です"
    Else
      str1 = "奇数です"
    End If
  Next i
  endTime = Timer
  Debug.Print "If文 :  " & (endTime - startTime)
  
  
  '結果は以下
'  IIf文 :  0.34375
'  If文 :  0.0703125
  '確かにIIfは遅いようだ
  
End Sub
```
### Static変数　(プロシージャを抜けても初期化されない変数)
```
Sub aaa()
    
    Call Xstatic
    Call Xstatic
    Call Xstatic
    Call Xstatic
    ' Xstatic()を4回呼び出すと、staticLongの値は初期化されておらず、最後の値は4になる
    ' Static変数は、プロシージャの実行が終わっても初期化されない変数

End Sub


Sub Xstatic()

    Static staticLong As Long
    
    staticLong = staticLong + 1
    MsgBox staticLong

End Sub
```
### vbCrLf と vbLfの違いについて　　※どちらも改行文字だが、使う場面は違う
```
vbCrLf は主にテキストファイル等の改行に使う。
セル内で改行した場合、vbLfになる。

Windowsの改行コードはvbCrLfなので、ファイル出力する場合はvbLfをvbCrLfに置き換えるべき

ただし、ファイルに出力する場合を除くと、vbLfのほうが無難だと思う。
UTF-8の4バイト文字(サロゲーロペア等)などを扱う場合、vbCrLfを入れてしまうと表示がおかしくなることがある

どうしてもという場合を除き、vbLfがいいのかもしれない
```
### Dictionary　　キーと値の組み合わせで登録する連想配列
```
キー値の重複を許さない。値のデータ型は、複数が混在できる　　Collectionと大体同じ機能みたい

'事前バインディング　Microsoft Scripting Runtime の参照が必要
Dim dic1 As Dictionary
Set dic1 = New Dictionary

'実行時バインディング
'Dim dic1 As Object
'Set dic1 = CreateObject("Scripting.Dictionary")

dic1.Add ("key1"), 100  'キー "key1" の値として、100を追加
dic1("key2") = "AAA"    'これでも追加できる キーの値の重複は不可
dic1("key3") = 123.456
dic1("key3") = 789.632  'キーの値が重複すると、後から指定したもので上書きされる

Debug.Print dic1.Keys(0)      '1番目の要素のキーの値を取得    key1
Debug.Print dic1.Items(0)     '1番目の要素の値を取得         100
Debug.Print dic1("key3")      'キーを指定して値を取得         789.632
Debug.Print dic1.Count        '要素数   3
Debug.Print dic1.CompareMode  '比較モード   0でバイナリモードらしい
Debug.Print dic1.Exists("key2")   ' キー "key2" の要素が存在するか  True

dic1.Remove ("key2") '要素を削除
Debug.Print dic1.Exists("key2")   ' False

Debug.Print "--------------------------"

'実行時バインディング
Dim dic2 As Object
Set dic2 = CreateObject("Scripting.Dictionary")

dic2(100) = 10000
dic2("key2") = "KEY2"
dic2(12.45) = 1200.4545
dic2("key4") = "KEY4444"
'キーの値は、文字列以外でも構わないらしい

Debug.Print dic2(12.45)   ' 1200.4545

'全ての値を取得するなら、以下のようなやり方が汎用性がある
Dim var1 As Variant
For Each var1 In dic2
  Debug.Print dic2(var1)
Next var1

Set dic2 = Nothing
```
### 空文字、値が0の文字列(vbNullString)、Empty値、Null値について
```
初期化したセルのValueはVariant型で、初期値はEmpty値らしい。さらに０と等しいと判定されることには十分に注意を

Sub aaa()

Dim str1 As String
Dim str2 As String

'空文字。長さ0の文字列
str1 = ""
Debug.Print Len(str1)   '0

'値が0の文字列
str2 = vbNullString
Debug.Print Len(str2)   '0

Debug.Print str1 = str2  'True
'Trueになるが、実際には、空文字とvbNullStringは別物

'StrPtr関数で、変数のアドレス値を取得
Debug.Print StrPtr(str1)  '161981044   これは毎回違う値
Debug.Print StrPtr(str2)  '0

Debug.Print "-----------------------------------"


'Empty値　Empty値を格納できるのはVariant型だけ
Dim var1 As Variant
Debug.Print IsEmpty(var1)  'True  初期化前のVariant型の値はEmpty
Debug.Print VarType(var1)  '0  Empty値
Debug.Print TypeName(var1) 'Empty

Debug.Print 0 = var1   'True  暗黙的に型変換され、0と等しいと判定される
Debug.Print "" = var1  'True  暗黙的に型変換され、空文字と等しいと判定される

Debug.Print "-----------------------------------"

Range(Cells(1, 1), Cells(2, 2)).Clear
var1 = Range(Cells(1, 1), Cells(2, 2)).Value  'セル範囲の値をVariant型へ格納
Debug.Print IsEmpty(var1(1, 1))  'True  値の無いセルはEmptyになる
Debug.Print IsEmpty(var1(2, 2))  'True

Range(Cells(1, 1), Cells(2, 2)).Clear
Range(Cells(1, 1), Cells(2, 2)).Value = ""
var1 = Range(Cells(1, 1), Cells(2, 2)).Value
Debug.Print IsEmpty(var1(1, 1))  'True  空文字でもEmpty
Debug.Print IsEmpty(var1(2, 2))  'True

Range(Cells(1, 1), Cells(2, 2)).Clear
var1 = Range(Cells(1, 1), Cells(1, 1)).Value  '1セルのみにする
Debug.Print VarType(var1)  '0  Empty値　1セルのみだと配列にならないので注意
Debug.Print IsEmpty(var1)  'True　　1セルのみで、そのセルの値が無いとEmptyになる

Cells(1, 1).Value = "AAA"
var1 = Range(Cells(1, 1), Cells(1, 1)).Value
Debug.Print VarType(var1)  '8  文字列型　　1セルのみでも値があればEmptyではない

Debug.Print "-----------------------------------"

'結合したセル範囲をVariant型に格納する時は注意
Application.DisplayAlerts = False
Range(Cells(1, 1), Cells(2, 2)).Clear
Range(Cells(1, 1), Cells(2, 2)).Value = 100
Range(Cells(1, 1), Cells(2, 2)).Merge   '4セルを結合する
var1 = Range(Cells(1, 1), Cells(2, 2)).Value
Debug.Print IsEmpty(var1(1, 1))  'False  左上のセルはEmptyではないが
Debug.Print IsEmpty(var1(1, 2))  'True  Emptyになる
Debug.Print IsEmpty(var1(2, 1))  'True
Debug.Print IsEmpty(var1(2, 2))  'True
'セルが結合されている場合の事を考慮に入れるように

Debug.Print "-----------------------------------"

'初期化したセルのValueはVariant型になり、Empty値が初期値らしい
Range(Cells(1, 1), Cells(2, 2)).Clear
Debug.Print Cells(1, 1).Value = ""   'True   これはいいのだが
Debug.Print Cells(1, 1).Value = 0    'True   暗黙的に型変換され、0と等しいと判定される

Cells(1, 1).Value = 123
Cells(1, 1).ClearContents
Debug.Print Cells(1, 1).Value = ""   'True   これはいいのだが
Debug.Print Cells(1, 1).Value = 0    'True   やっぱりTrueになってしまう

Debug.Print "-----------------------------------"


'Null値　Null値を格納できるのはVariant型だけ
var1 = Null 'Nullを格納
Debug.Print VarType(var1)   '1  Null値
Debug.Print IsNull(var1)    'True

'普通はNullを扱うことは少ないが、データベースと接続すると使う機会が多くなるだろう

End Sub
```
### モジュールのグローバル変数はプロシージャから抜けてもメモリから解放されずに値が残るので注意
```
配列型・Variant型・Object型は最終処理で初期化するように。Variant型はNothingをSetするといいみたい

Option Explicit

Private globalStaticArray(0 To 5) As String
Private globalDynamicArray() As Long
Private glovalVariant As Variant
Private globalLong As Long

Sub aaa()
  Dim localStaticArray(0 To 4) As String
  Dim localDynamicArray() As Long
  Dim localVariant As Variant
  Dim localLong As Long
  
  'グローバル変数の配列は、2回目以降では値が残っている
  globalStaticArray(0) = "ABC"
  ReDim globalDynamicArray(3)
  globalDynamicArray(0) = 123
  ReDim glovalVariant(5)
  glovalVariant(0) = "KKJ"
  globalLong = 123456
  
  'ローカル変数の配列は値が残っていない
  localStaticArray(0) = "WWW"
  ReDim localDynamicArray(3)
  localDynamicArray(0) = 999
  ReDim localVariant(5)
  localVariant(0) = 789
  localLong = 999998
  
End Sub

Sub BBB()
  Dim localStaticArray(0 To 4) As String
  Dim localDynamicArray() As Long
  Dim localVariant As Variant
  Dim localVariant222 As Variant
  
  '2回目以降の処理でも、メモリは解放されているはず
  globalStaticArray(0) = "ABC"
  ReDim globalDynamicArray(3)
  globalDynamicArray(0) = 123
  ReDim glovalVariant(5)
  glovalVariant(0) = "KKJ"
  
  localStaticArray(0) = "WWW"
  ReDim localDynamicArray(3)
  localDynamicArray(0) = 999
  ReDim localVariant(5)
  localVariant(0) = 789
  
  'グローバル変数の配列はEraseで解放すべき
  Erase globalStaticArray
  Erase globalDynamicArray
  Erase glovalVariant
  'Variant型の変数でも、Redimで領域確保すればEraseできる
  'セル範囲をVariant型に格納する場合、セル範囲が1セルだけでその値が空白だとEmptyになることは注意
  
  'Erase localVariant222     'これはエラーになる。配列になっていないため
  
  Set localVariant222 = Nothing   'これは可能
  
  'Variant型の変数は、Nothingをセットするのが適切か。
  'グローバル変数であっても、Nothingの状態からReDimは可能なようだ。セル範囲も格納できる
  Set glovalVariant = Nothing
  ReDim glovalVariant(3)
  Set glovalVariant = Nothing
  glovalVariant = ActiveSheet.Range(Cells(1, 1), Cells(5, 5)).Value
  
End Sub


Sub CCC()

Dim var1 As Variant
Dim var2 As Variant

ReDim var1(3)   '配列として領域確保
Erase var1      'これはOK
'Erase var2      'これはエラーになる。配列になっていないので

'Variant型変数は、NothingをSetするのが適切らしい。EmptyはSetできない
Set var2 = Nothing
ReDim var2(3)
Set var2 = Nothing
var2 = ActiveSheet.Range(Cells(1, 1), Cells(5, 5)).Value

End Sub
```
### Variant型の変数の初期化　　NothingをSetするのが適切か
```
Dim var1 As Variant
Dim var2 As Variant

ReDim var1(3)   '配列として領域確保
Erase var1      'これはOK
'Erase var2      'これはエラーになる。配列になっていないので

'Variant型変数は、NothingをSetするのが適切らしい。EmptyはSetできない
Set var2 = Nothing
ReDim var2(3)
Set var2 = Nothing
var2 = ActiveSheet.Range(Cells(1, 1), Cells(5, 5)).Value
```