「標準モジュール」とは何ものなのか

「標準モジュール」とは何ものなのか

割と最近まで、「標準モジュール」というのは、単に【コードを書く場所】ぐらいの雑なとらえ方で済ませていた。

しかし、「クラスモジュール」とか、「フォームモジュール」、「シートモジュール」、「ThisWorkbookモジュール」などを使い分けていくうちに、改めて「標準モジュールとは何ものなのか」という疑問が生じたわけである。

標準モジュールはNewできない

たとえば、標準モジュールを挿入し、オブジェクト名を「Foobar」とする。

f:id:akashi_keirin:20180406165212j:plain

で、こいつをNewしようとすると、

f:id:akashi_keirin:20180406165221j:plain

「不正」呼ばわりw

標準モジュールというものは、「オブジェクト」ではあるものの、インスタンス化はできない模様。

ちなみに、FormモジュールはNewできる。

akashi-keirin.hatenablog.com

静的(static)クラスっぽい?

インスタンス化できない、といえば、静的クラスみたいなもんだろうか。

ちょっとメソッドを持たせてみる。

リスト1 標準モジュール「Foobar」
Option Explicit

Public Sub hoge()
  Call makeUserSick("ホゲーーー♪")
End Sub

Private Sub fuga()
  Call makeUserSick("ふがふが")
End Sub

※makeUserSickメソッドのコードはコチラ

PublicメソッドとPrivateメソッドをそれぞれ1つづつ持たせてみた。

Publicメソッドだと、

Foobar.hoge

とモジュール名を書かなくても、

hoge

だけで呼び出せてしまって、イマイチ。

もし、

akashi-keirin.hatenablog.com

このときのように、モジュール名を指定すればPrivateメソッドでも実行できるとなれば便利だと思った。

次のコードで実行してみた。

テストコード 標準モジュール「Module1」
Public Sub testFoobar()
  Call hoge
  Call fuga
End Sub

これは、当然

f:id:akashi_keirin:20180406165231j:plain

エラーになる。当たり前だ。

次は、Privateなfugaメソッドにモジュール名を指定してみる。

テストコード 標準モジュール「Module1」
Public Sub testFoobar()
  Call hoge
  Call Foobar.fuga
End Sub

f:id:akashi_keirin:20180406165241j:plain

コーディング中にintellisenseが働かないのはあのときと同じなので気にしない。

すると、

f:id:akashi_keirin:20180406165249j:plain

アチャー、ダメかw

残念!

モジュール名を指定しないと使えない、とかだったら便利だと思ったんだけどなあ。

ならばクラス(Static)変数はどうか

標準モジュールでもPropertyを持たせることができるので、クラス変数的なものは持たせられるのではないか、と考えた。

リスト1に次のようにコードを追加する。

スト2 標準モジュール「Foobar」
Option Explicit

Private Foo_ As Integer

Public Property Let Foo(ByVal bar As Integer)
  Foo_ = bar
End Property

Public Property Get Foo() As Integer
  If Foo_ = 0 Then Foo_ = 1
  Foo = Foo_
End Property

Public Sub hoge()
  Call makeUserSick("ホゲーーー♪" & " Fooプロパティは「" & Foo & "」やでw")
  Foo_ = Foo_ + 1
End Sub

Public Sub fuga()
  Call makeUserSick("ふがふが" & " Fooプロパティは「" & Foo & "」やでw")
  Foo_ = Foo_ + 1
End Sub

Fooというプロパティを設定し、hoge、fugaメソッドを実行するたびにFooプロパティをインクリメントする。

たったこれだけ。Fooプロパティの値は、hoge、fugaメソッド実行時に表示されるようにしてある。

んで、次のコードを何度か実行してみる。

テストコード 標準モジュール「Module1」
Public Sub testFoobar()
  Call Foobar.hoge
  Call Foobar.fuga
End Sub
実行1回目

f:id:akashi_keirin:20180406165300j:plain

f:id:akashi_keirin:20180406165310j:plain

Fooプロパティは、「2」になっている。

実行2回目

再度実行してみる。

f:id:akashi_keirin:20180406165322j:plain

f:id:akashi_keirin:20180406165331j:plain

FoobarのFooプロパティは、testFoobarプロシージャの実行終了後も値を保持し続けている。

クラス変数っぽいといえばクラス変数っぽいけれど、そもそもインスタンス化できないのだから、何に役立つのか分からないw

ちなみに、オブジェクト名「Room」というクラスモジュールの宣言セクションに「numberOfRooms」というPublic変数を置いて、

Dim r As New Room
Debug.Print Room.numberOfRooms

と書いたとしても、実行すると

f:id:akashi_keirin:20180406165344j:plain

無情にもコンパイル・エラーになる。

まとめ

およそ、次のようなことが分かった。

  • 標準モジュールはNewできない。
  • モジュール名を指定してもPrivateメソッドは別モジュールから呼び出せない。
  • プロパティを持たせることで、クラス(static)変数っぽいことはできる。
  • クラスモジュールではクラス(static)変数を持たせることができない。

おわりに

「標準モジュール」、「クラスモジュール」ともに一長一短があるんだよなあ……。

@akashi_keirin on Twitter

配列変数を値渡しにする

値渡しの配列引数

配列引数を値渡しにする方法

akashi-keirin.hatenablog.com

このときにも書いたとおり、通常、プロシージャの引数に配列を渡すとき、

Public Sub hogehoge(ByVal foo() As String)

みたいにすると、

f:id:akashi_keirin:20180401225819j:plain

こんなエラーが出る。

ところが、VBA四天王(だから、内わけはどうなっとるんだよw)の一人、id:imihito さん曰く、

引数の型をVariant型にすると、配列も値渡しで受け取ることができたりします

とのこと。

さらに、

オブジェクト型配列だと型情報が抜ける

とも。

へえ。いっぺんやってみよう。

Variant型引数にオブジェクト型の配列を渡してみる

メンドクサイので、2ついっぺんにやってみる。

リスト1 標準モジュール
Public Sub testDeclareObjectArgByVal()
  Dim Sh As Worksheet
  Set Sh = ThisWorkbook.Worksheets("Sheet1")
  Dim vtlTable(1) As New VirtualTable
  vtlTable(0).init Sh.Range("A1").CurrentRegion    '……(1)'
  vtlTable(1).init Sh.Range("J1").CurrentRegion
  Call returnValue(vtlTable)    '……(2)'
End Sub

Private Sub returnValue(ByVal targetVirtualTable As Variant)    '……(3)'
  Dim i As Integer
  For i = LBound(targetVirtualTable) To UBound(targetVirtualTable)    '……(4)'
    Debug.Print targetVirtualTable(i).valueOfCell(2, 2)
  Next
  Debug.Print TypeName(targetVirtualTable)    '……(5)'
End Sub

せっかくなので、

akashi-keirin.hatenablog.com

前回リニューアルしたVirtualTableクラス型の配列を使ってやってみた。

VirtualTableクラスのコードについてはコチラをどうぞ。

f:id:akashi_keirin:20180401225826j:plain

こんな表を用意して、

(1)からの2行

vtlTable(0).init Sh.Range("A1").CurrentRegion
vtlTable(1).init Sh.Range("J1").CurrentRegion

で、initメソッドを用いて、A1セルのCurrentRegionをvtlTable(0)に、J1セルのCurrentRegionをvtlTable(1)に渡してそれぞれ初期化する。

(2)の

Call returnValue(vtlTable)

では、引数にVirtualTableクラス型の配列変数vtlTableを渡してreturnValueプロシージャを呼び出している。

returnValueプロシージャは、(3)からの7行

Private Sub returnValue(ByVal targetVirtualTable As Variant)    '……(3)'
  Dim i As Integer
  For i = LBound(targetVirtualTable) To UBound(targetVirtualTable)    '……(4)'
    Debug.Print targetVirtualTable(i).valueOfCell(2, 2)
  Next
  Debug.Print TypeName(targetVirtualTable)    '……(5)'
End Sub

(3)の

Private Sub returnValue(ByVal targetVirtualTable As Variant)

で、引数の型をVariant型にして、ByValキーワードを付けている。

見た目上、値渡しになっているが、その反面、見た目上は引数targetVirtualTableが配列変数だとは全く分からない。

このプロシージャ内では、まず(4)からの3行

For i = LBound(targetVirtualTable) To UBound(targetVirtualTable)
  Debug.Print targetVirtualTable(i).valueOfCell(2, 2)
Next

で、VirtualTableクラスのインスタンスそれぞれが内部で保持している2次元配列(元は表)の2行2列目の値をイミディエイトに書き出し、それが終わったら、(5)の

Debug.Print TypeName(targetVirtualTable)

で、TypeName関数を用いて、引数として受け取ったtargetVirtualTableの型名をイミディエイトに出力する、という至って投げやりな処理を行う。

実行結果

f:id:akashi_keirin:20180401225834j:plain

おお! 3行目が「Object()」になっとる!

たしかに、もともとVirtualTable型であったという情報は失われているっぽい。

おわりに

この他にも、呼び出され側のプロシージャ(今回の場合はreturnValueプロシージャ)をコーディングするとき、targetVirtualTableの型が未確定なため、Intellisenseが全く効かず、非常に書きにくかった、ということも申し添えます。

わざわざ値渡しにする意義が今ひとつ見いだせなかった。

VirtualTableクラスは今

VirtualTableクラスは今

f:id:akashi_keirin:20180401123338p:plain

VirtualTableクラスの現状

VLOOKUPにせよ、INDEX & MATCHの合わせ技にせよ、セルに数式がずらずらと書き込まれているというのはちょっとイヤなので、表引きは極力VBAでやっている。

んで、VLOOKUPみたいな働きを持ったクラスを作ったのが

akashi-keirin.hatenablog.com

このときの試み。

いろいろと使っていくうちに、今こんなふうになっている、というのをうpしておくことにした。

リスト1 クラスモジュール

オブジェクト名は「VirtualTable」。

Option Explicit

Private Type Exception
  Number_ As Integer
  Discription_ As String
End Type

Private Const NUM_NOT_INIT As Integer = 10001
Private Const DISCRIPT_NOT_INIT As String = _
                "VirtualTableクラスのinitメソッド未実行"

'Member Variable'
Private thrownException10001 As Exception

Private isInitialized_ As Boolean
Private tableArray_ As Variant
Private rowsCount_ As Long
Private columnsCount_ As Long

'Accessor'
Public Property Get rowsCount() As Long
  If Not isInitialized_ Then Call catchException(thrownException10001)
  rowsCount = rowsCount_
End Property
Public Property Get columnsCount() As Long
  If Not isInitialized_ Then Call catchException(thrownException10001)
  columnsCount = columnsCount_
End Property
Public Property Get valueOfCell(ByVal rowIndex As Long, _
                                ByVal columnIndex As Long) As Variant
On Error GoTo errorHandler
  If Not isInitialized_ Then Call catchException(thrownException10001)
  valueOfCell = tableArray_(rowIndex, columnIndex)
  Exit Property
errorHandler:
  valueOfCell = False
End Property

'Constructor'
Private Sub Class_Initialize()
  With thrownException10001
    .Number_ = NUM_NOT_INIT
    .Discription_ = DISCRIPT_NOT_INIT
  End With
End Sub

Public Sub init(ByVal targetTableRange As Range)
On Error GoTo errorHandler
  tableArray_ = targetTableRange.Value
  rowsCount_ = UBound(tableArray_, 1)
  columnsCount_ = UBound(tableArray_, 2)
  isInitialized_ = True
  Exit Sub
errorHandler:
End Sub

'Method'
Public Function searchValueVertical( _
                  ByVal searchFor As Variant, _
                  Optional ByVal searchColumn As Long = 1, _
                  Optional ByVal returnValueColumn As Long = 1) As Variant
On Error GoTo errorHandler
  If Not isInitialized_ Then Call catchException(thrownException10001)
  Dim i As Long
  Dim tmp As Variant
  For i = 1 To rowsCount_
    tmp = tableArray_(i, searchColumn)
    If tmp = searchFor Then
      searchValueVertical = tableArray_(i, returnValueColumn)
      Exit Function
    End If
  Next
errorHandler:
  searchValueVertical = False
End Function

Private Sub catchException(ByRef thrownException As Exception)
  With thrownException
    Call Err.Raise(Number:=.Number_, _
                   Description:=.Discription_)
  End With
End Sub

大きく変えたのは、initメソッド未実行のときにエラーを吐くようにしたところですかね。

エラー情報の管理のために無駄に構造体を使ってみたりもしています。

現時点での仕様

プロパティ
rowsCount(Long型)

メンバ変数tableArray_として保持している表(正体は2次元配列)の行数。

columnsCount(Long型)

メンバ変数tableArray_として保持している表(正体は2次元配列)の列数。

valueOfCell(Variant型)

メンバ変数tableArray_として保持している表(正体は2次元配列)のrowIndex行columnIndex列の値。

メソッド
init

式.init(targetTableRange)

引数targetTableRangeには、Range型の表を指定する。

searchValueVertical

式.searchValueVertical(searchFor, [searchColumn], [returnValueColumn])

引数searchForには、検索したい値をVariant型で指定する。

引数searchColumn(省略可)には、検索値を検索する対象の列番号をLong型で指定する。デフォルト値は「1」。

引数returnValueColumn(省略可)には、検索値が見つかったときに、検索値のあった行のどの列の値を返すのかをLong型で指定する。デフォルト値は「1」。

返り値はVariant型。表の指定した列(searchColumn列)の値を上から順に調べ、最初にsearchForとマッチした行の指定した列(returnValueColumn列)の値を返す。

検索値がヒットしなかった場合、または検索時にエラーとなった場合(存在しない列を指定するなど)には、Falseが返る。

おわりに

便利な機能を追加したいけど、アイディアがない。

 

akashi-keirin.hatenablog.com

 

配列を引数にするときの注意

配列を引数にするときの注意事項

配列引数はByRefでなければなりません

SubとかFunctionの引数に配列を使用しようとしたとき、「ByVal」キーワードを付けると、

f:id:akashi_keirin:20180327160042j:plain

こんなふうにコンパイル・エラーになる。

従って、引数を配列にしたときには必然的に参照渡しになってしまう、ということだ。

配列を加工して返すFunctionを使用した場合

……ということは、配列を受け取って、加工して返すようなFunctionを使用した場合、もはや元の(加工前の)配列は存在しない、ということなのだろうか。

やってみた

次のコードで実験。

リスト1 標準モジュール
Public Sub testTemperArray()
  Dim ar(3) As String
  ar(0) = "アホ"
  ar(1) = "ボケ"
  ar(2) = "クズ"
  ar(3) = "デコスケ"
  Dim el As Variant
  Debug.Print "【arの要素書き出し】"
  For Each el In ar    '……(1)'
    Debug.Print el
  Next
  Dim ar2() As String
  ar2 = temperArray(ar)    '……(2)'
  Debug.Print "【ar2の要素書き出し】"
  For Each el In ar2    '……(3)'
    Debug.Print el
  Next
  Debug.Print "【再びarの要素書き出し】"
  For Each el In ar    '……(4)'
    Debug.Print el
  Next
End Sub

Private Function temperArray(ByRef targetArray() As String) As String()
  targetArray(2) = "KASU"
  temperArray = targetArray
End Function

「temper」というのは、今はやりの「改竄する」という意味w

受け取った配列の第3要素を「KASU」というイカしたローマ字に改竄して返すプロシージャだ。

まず、冒頭で作成したarという要素数4の配列について、(1)からの3行

For Each el In ar
  Debug.Print el
Next

で、For Eachを使って全要素をイミディエイトに書き出す。

次に、(2)の

ar2 = temperArray(ar)

で、バカ丸出しのFunctionプロシージャ「temperArray」に配列arを渡して、返り値をar2にぶち込む。

そして、(3)からの3行

For Each el In ar2
  Debug.Print el
Next

で、ar2の要素をイミディエイトに書き出す。

あとは、(4)からの3行

For Each el In ar
  Debug.Print el
Next

で、再度arの全要素をイミディエイトに書き出す。

実行結果

f:id:akashi_keirin:20180327160049j:plain

arを引数として渡して加工したときに、arそのものが加工されてしまったっぽい。まさに参照渡し。

おわりに

気をつけないといけないなあ。

文字列を比較演算子で比較するとどうなるか

文字列を比較演算子で比較する

文字列を比較演算子で比較した結果

何の気なしに、イミディエイトに次のように打ち込んで[Enter]してみた。

?"ち~んw" < "た~んw"

すると、

f:id:akashi_keirin:20180327084850j:plain

このように、Falseが返った。

んで、

?"ち~んw" < "つ~んw"

と入力して[Enter]すると、

f:id:akashi_keirin:20180327084909j:plain

このように、Trueが返った。

……ということは、文字列でもある程度ソートできるということだ。

もちろん、Excelのワークシートの機能を使えば楽勝なんだけれど、あくまでもプログラミングの練習、ということで。

配列を昇順バブルソートするFunction

ちょっと荒っぽいコードだけれど、配列を渡したら、配列の要素を昇順ソートした配列を返すFunctionのコードを書いてみた。

リスト1 標準モジュール
Public Function getSortedArrayByBubbleSort( _
                  ByRef targetArray As Variant) As Variant
  Dim buf As Variant
  Dim i As Long
  Dim j As Long
  For i = UBound(targetArray) To LBound(targetArray) Step -1    '……(1)'
    For j = LBound(targetArray) + 1 To i    '……(2)'
      If targetArray(j - 1) > targetArray(j) Then    '……(3)'
        buf = targetArray(j)    '……(4)'
        targetArray(j) = targetArray(j - 1)    '……(5)'
        targetArray(j - 1) = buf    '……(6)'
      End If
    Next
  Next
  getSortedArrayByBubbleSort = targetArray
End Function

(1)からの大ループと、(2)からの小ループの2重ループ。

大ループでは、引数で渡された添字の最大値から、最小値に向かって添字を減らしながらループする。

内側の小ループでは、2番目の要素から外側のループカウンタで示される要素までループする。

たとえば、引数で渡された配列targetArrayが5つの要素を持っているとすると、添字は

0,1,2,3,4

の5つ。

従って、大ループの2回目だと、小ループ内では、

targetArray(1)→targetArray(2)→targetArray(3)→……

の順で処理をすることになる。

(3)からの5行

If targetArray(j - 1) > targetArray(j) Then
  buf = targetArray(j)    '……(4)'
  targetArray(j) = targetArray(j - 1)    '……(5)'
  targetArray(j - 1) = buf    '……(6)'
End If

では、targetArray(j)の値を、隣の値targetArray(j - 1)と比較し、targetArray(j - 1)の方が大きかったら、

まず(4)の

buf = targetArray(j)

でtargetArray(j)の値を一時変数bufに格納し、

(5)の

targetArray(j) = targetArray(j - 1)

でtargetArray(j)にtargetArray(j - 1)の値を代入し、

(6)の

targetArray(j - 1) = buf

で先ほどbufに入れておいた元のtargetArray(j)の値をtargetArray(j - 1)に代入することにより、targetArray(j)とtargetArray(j - 1)のそれぞれの中身を交換している。

これを繰り返すことにより、

小ループが終わると、一番大きな値が最大の添字の要素になっている

ということだ。

全くイメージが湧かないと思うので、要素の動きを羅列してみよう。

もともと、targetArray(0)~targetArray(4)が、

4,2,5,3,1

の順に並んでいるとする。

大ループ1回目(i = 4)すなわち小ループ1周目

大ループの1回目は、小ループの1周目。すなわち、j = 0 + 1 から j = 4 までである。

j = 1

2,4,5,3,1

j = 2

2,4,5,3,1

j = 3

2,4,3,5,1

j = 4

2,4,3,1,5

大ループ2回目(i = 3)すなわち小ループ2周目

大ループの2回目は、小ループの2周目。すなわち、j = 0 + 1 から j = 3 までである。

j = 1

2,3,4,1,5

j = 2

2,3,4,1,5

j = 3

2,3,1,4,5

大ループ3回目(i = 2)すなわち小ループ3周目

大ループの3回目は、小ループの3周目。すなわち、j = 0 + 1 から j = 2 までである。

j = 1

2,3,1,4,5

j = 2

2,1,3,4,5

大ループ4回目(i = 1)すなわち小ループ4周目

大ループの4回目は、小ループの4周目。すなわち、j = 0 + 1 から j = 1 までである。

j = 1

1,2,3,4,5

1周目の大ループで、必ず1番大きな数が右端に追いやられ、2周目の大ループで2番目に大きな数が右端から2番目に追いやられ……を繰り返すことによって、左から昇順にソートしたことになるのが分かる。

実験

次のコードで、リスト1のFunctionを使ってみる。

スト2 標準モジュール
Public Sub testSortElementsByBubbleSort()
  Dim ar As Variant
  Dim el As Variant
  Dim i As Integer
  ar = getSortedArrayByBubbleSort( _
         Array("ち~んw", "て~んw", "た~んw", "つ~んw"))
  For Each el In ar
    Debug.Print el
  Next
End Sub

見ての通り、「ち~んw」、「て~んw」、「た~んw」、「つ~んw」の4要素からなる配列をgetSortedArrayByBubbleSortに渡して、返り値の要素を順に取り出して出力するだけのコード。

実行結果

f:id:akashi_keirin:20180327084918j:plain

無事、思っていた通りの出力が得られた。

おわりに

ワークシートの機能を使えば楽勝な機能を、あえてコーディングだけで実現する、というのは面白い。

@akashi_keirin on Twitter

Not演算子は何をしているのか

Not演算子は何をしているのか

Boolean型の式の判定

もともと、

If isHoge = True Then fooBar

みたいな書き方をする方だった。

If isHoge Then fooBar

みたいな書き方が、何か大切なことを省略しているような気がしてイヤだったのだ。

ところが、あるときthomさんのブログを読んで、

そういや、(hoge > fuga) = True なんていちいち書かないよなー

と、己の行動の矛盾に気がついて以来、IfとかDo Whileなんかの条件式部分にTrue / Falseは書かない派になった。

Not演算子の正体

したがって、上の例でたとえば、

If isHoge = False Then fooBar

としたいようなときには、

If Not isHoge Then fooBar

と書くことになる。

で、このNotの正体を調べてみた。

イミディエイトにいろいろ打ち込んで調べてみると、

f:id:akashi_keirin:20180327083543j:plain

こんな感じ。

要するに、

  • Not hoge
    は、
    -1 - hoge
    のことらしい
  • Not True
    は、
    -1 - (-1)
    のことなのでFalse(0)になる
  • Not False
    は、
    -1 - 0
    のことなのでTrue(-1)になる

ということらしい。

おわりに

はたして、この推理は正しいのだろうか……。

どの道、よく考えられているなあ。

@akashi_keirin on Twitter

VBAで擬似continueを実現する(失敗)

擬似continue

VBAのイマイチなところ選手権」でもやったら、かなり上位に食い込むだろうと思うのが、

Forループでcontinueが使えない

ことだと思う。

breakに相当する命令があるだけに、なおのこと歯痒いことだろう。

テスト用コード

リスト1 標準モジュール
Public Sub testContinue()
  Dim ar As Variant
  ar = Array("アホ", "バカ", "クズ", _
             "カス", "デコスケ", "ロクデナシ", _
             "ウジムシ", "ボケ", "スットコドッコイ", _
             "ち~んw")
  Dim i As Long
  For i = 0 To 9
    Debug.Print ar(i)
  Next
End Sub

素数10の配列を準備して、各要素をDebyg.Printでイミディエイトに書き出すだけのコード。

たとえば、こいつを「3の倍数個目は出力しない」みたいな条件で作動させることを考える。

Forブロック内部をIfブロックにおさめる

continueしたい条件のときは何もしない、ということを、If文で実現する考え方。

リスト2-1 標準モジュール
Public Sub testContinue()
  Dim ar As Variant
  ar = Array("アホ", "バカ", "クズ", _
             "カス", "デコスケ", "ロクデナシ", _
             "ウジムシ", "ボケ", "スットコドッコイ", _
             "ち~んw")
  Dim i As Long
  For i = 0 To 9
    If ((i + 1) Mod 3) = 0 Then
    Else
      Debug.Print ar(i)
    End If
  Next
End Sub

もちろん、これで意図した通りの出力は得られるが、処理の本体がそこそこのサイズの場合、全体をIfブロックの中身にする、というのはあまりにブサイク。

Nextの手前にラベルを置いてGoToでジャンプする

題名の通り。Nextの直前にラベルを置いておいて、continueする代わりにGoToでジャンプするやり方。

リスト2-2 標準モジュール
Public Sub testContinue()
  Dim ar As Variant
  ar = Array("アホ", "バカ", "クズ", _
             "カス", "デコスケ", "ロクデナシ", _
             "ウジムシ", "ボケ", "スットコドッコイ", _
             "ち~んw")
  Dim i As Long
  For i = 0 To 9
    If ((i + 1) Mod 3) = 0 Then GoTo JumpTo
    Debug.Print ar(i)
JumpTo:
  Next
End Sub

For文の入り口で条件を確認して、条件に当てはまっていたらNextの手前まで飛ぶので、これでもまあ、意図通りの出力が得られるだろう。

ただ、ラベルは必ず左端に配置されてしまうので、インデントが狂ってしまう。これまたブサイク。

プロシージャを呼び出してループカウンタをインクリメントする

ちょっとこういうおバカなやり方を実験してみた。continueする条件に当てはまっていたら、ループカウンタをその場でインクリメントしてみるw

スト2-3 標準モジュール
Public Sub testContinue()
  Dim ar As Variant
  ar = Array("アホ", "バカ", "クズ", _
             "カス", "デコスケ", "ロクデナシ", _
             "ウジムシ", "ボケ", "スットコドッコイ", _
             "ち~んw")
  Dim i As Long
  For i = 0 To 9
    If ((i + 1) Mod 3) = 0 Then continue i
    Debug.Print ar(i)
  Next
End Sub

Public Sub continue(ByRef i As Long, _
                    Optional ByVal step_ As Integer = 1)
  i = i + step_
End Sub

ループカウンタを参照渡しでcontinueプロシージャに渡す。受け取ったcontinueプロシージャ側では、ループカウンタをインクリメントするだけ。値渡しだと、「i」は呼び出し側と呼び出され側で別物扱いになるので、珍しく参照渡し。これだとcontinueプロシージャ側での計算結果が呼び出し元の「i」にも反映される。

実行結果

f:id:akashi_keirin:20180326071330j:plain

いちおう、意図通りの出力が得られた。

おわりに

もちろん、3つ目のやり方が重大な問題(笑)をはらんでいる(したがって使いものにならない)ことは重々承知の上ですw

@akashi_keirin on Twitter