えくせるちゅんちゅん

ことりがエクセルをちゅんちゅんするブログ

VBAクイズ 問9~問13解説 VBAの各種文法の注意事項

今回は先日投稿したクイズの問9~問13のVBAの文法の注意事項について解説していきたいと思います。

f:id:Kotori-ChunChun:20190616230553p:plain


前の記事

出題ページ

kotori-chunchun.hatenablog.com

問1~問5の解説 Excel VBAのRange操作の注意事項

kotori-chunchun.hatenablog.com

問6~問8の解説 VBAの配列処理の注意事項

kotori-chunchun.hatenablog.com

解説について

まず初めに申し上げて起きますが、ここで解説する内容は実際に実行した結果から中の動きを推測して説明しているものです。

全ての説明にMicrosoft公式の根拠があるわけではないため、正確性に欠けるかもしれない点にご注意ください。

また誤りや不明な点がありましたら、遠慮なく教えてください。

基礎知識

まずは本記事で使用しているプロパティのリンク集を貼っておきます。

解説

問9 サブルーチンへの参照渡しによる値の書き換えができない問題

Sub q_09()
    Dim x As Long
    x = 10
    q_09_func (x)
    Debug.Print "" & x
End Sub

Sub q_09_func(ByRef a As Long)
    a = a * 2
End Sub
設問 正解
1.           10

みんな大好き?「ByRef:参照渡し」に関する問題です。

まずq_09_funcですが、要するに「a」を二倍した状態で呼び出し元に戻すプロシージャですね。

ところが、現実には二倍されていない・・・。

これには呼び出し元の構文q_09_func (x)に問題があります。

先日オトモダチの模索中さんがVBAfunc (a , b)を呼び出すとエラーになるという説明をしていました。

infoment.hatenablog.com

要するに、この中で記述されている

Callを付けない時の()は、計算式の()なんですよ。 例えば、(3+4)×5 のような時に使う()ですね。

がヒントです。

何故「a」が二倍されないかと言うと・・・私の尊敬するthom様が超詳しい解説をしているので、こちらを参照して下さい。

thom.hateblo.jp

重要な部分だけを抜粋すると

カッコをつけると引数が値渡しになるのだ。 (a)が評価されて10になった後に渡されるから、値渡しになる。

というわけです。

ByRefなのに参照にならない事があるなんて怖いですが、そういうものだと飲み込んで下さい。

あとは私の持論ですが、

引数1個の関数はエラーが出ないからCall付けなくてOK♪ なんて考えてる人は大間違いです。

VBAにおいて、戻り値有りはret = func(a)、戻り値無しはCall func(a)この二択しか有りえません。

他言語のようなfunc(a)は間違いだと思って下さい。

ちなみにfunc(a)のまま確定するとfuncスペース(a)に変化しますから、勝手に補正された時は要注意ですから一度見直すと良いかも知れません。

問10 Formatで日付の正しく取得できない問題

Sub q_10()
    Dim dtm As Date
    dtm = #1/2/2018 3:04:05 AM#
    Debug.Print "時 : " & Format(dtm, "hh")
    Debug.Print "分 : " & Format(dtm, "mm")
    Debug.Print "秒 : " & Format(dtm, "ss")
End Sub
設問 正解
1.           時 : 03
2.           分 : 01
3.           秒 : 05

これは「知らなければ意味が分からない。知ってても見落としやすい問題」ですね。

(1)と(3)はフェイクです。どうってことありません。出題したかった問題は(2)です。

このプログラムを書いた人は「分:4」と出力されてほしかったはずですが、実際には「分:1」と出力されます。

#1/2/2018 3:04:05 AM#に含まれる「1」はと言うと・・・「月=Month」ですね。

日時データってMonthとMinuteでイニシャルが競合するんですよねー。

これVBAに限った話ではなくて、様々な言語で発生する問題で、言語毎に独特の対策が取られています。(それがまた面倒くさい)

というわけでFormat 関数を御覧ください。

転載するとMSには次のように書かれています。

記号 範囲
h 0 〜 23 (「AM」または「PM」を追加した 1 〜 12 の数値) (前に 0 を付けない、1 日の中の時間)
hh 00 〜 23 (「AM」または「PM」を追加した 01 〜 12 の数値) (前に 0 を付ける、1 日の中の時間)
n 0 〜 59 (前に 0 を付けない、1 時間の中の分)
nn 00 〜 59 (前に 0 を付ける、1 時間の中の分)
m 0 〜 59 (前に 0 を付けない、1 時間の中の分) h または hh が先行する場合のみ
mm 00 〜 59 (前に 0 を付ける、1 時間の中の分) h または hh が先行する場合のみ
s 0 〜 59 (前に 0 を付けない、1分の中の秒)
ss 00 〜 59 (前に 0 を付ける、1分の中の秒)

つまりFormat(dtm, "hhmmss")なら時時分分秒秒になるけど、Format(dtm, "mm")だと月月になってしまうんですね。

で、正しくはFormat(dtm, "nn")と。

なんてややっこしい!

通常、月だけを取り出したい場面ではMonth(dtm)とすれば良いので、滅多に無いんですけどねー。稀に二桁月を取り出したくて使うんです。んで間違えると。

皆さんも気をつけましょう。

問11 Splitで1つ目の項目が出力されない問題

Sub q_11()
    Dim s() As String
    Dim i As Long
    s = Split("1,2,3", ",")
    For i = 1 To UBound(s)
        Debug.Print s(i)
    Next
End Sub
設問 正解
1.           2
2.           3

これは基本ですが「知ってても間違えやすい問題」ですね。

s = Split("1,2,3", ",")を実行すると変数の中身は下記のようになります。

変数
s(0) 1
s(1) 2
s(2) 3

そして、ループFor i = 1 To UBound(s)の開始条件が「1」になっているので、0番目の1が出力されない寸法ですね。

修正は簡単♪

For i = LBound(s) To UBound(s)とするだけ♪

sの宣言を単体Dim s As StringとしてFor Each s in Split("1,2,3", ",")なんてのも無難かな?

というわけでサービス問題でした。

問12 On Error Resume Nextの挙動に関する問題

Sub q_12()
    On Error Resume Next
    If Int("エラーよ起これ!") Then
        Debug.Print "Then"
    Else
        Debug.Print "Else"
    End If
End Sub
設問 正解
1.           Then

VBのエラー処理が糞だと言われる所以の一つです・・。

Resume Nextはエラーが起きたら無条件でThenが実行されます。

そのため、このような時は事前に結果を変数に格納して分岐するか、On Error Goto ラベル文でEnd Ifの先まで飛ばして下さい。

問13 Ifの式に関する問題

Sub q_13()
    If 3 > 2 > 1 Then
        Debug.Print "Then"
    Else
        Debug.Print "Else"
    End If
End Sub
設問 正解
1.           Else

3 > 2 > 1は数学の知識そのままにコーディングした感じですが、VBAにおいてはこれは危険です。

Ifについて正しく理解していれば、間違っていることは一目瞭然なんですが・・・説明しろと言われると長くなりそうです。

そこで難しい説明は置いといて、これだけ見せれば理解してもらえる思うのですが如何でしょうか。

Sub q_13_test1()
    Debug.Print (3 > 2 > 1) ' = False
    '----式の展開----
    Debug.Print (3 > 2)     ' = True
    Debug.Print Int(True)   ' = -1
    Debug.Print (-1 > 1)    ' = False
End Sub

Sub q_13_test2()
    If True Then
        Debug.Print "Then"
    Else
        Debug.Print "Else" '←絶対に到達できないコード
    End If
End Sub

例えばSheet_BeforeDoubleClickイベントの中の、5 < Target.Row < 10みたいな時だと、ついついこう書きたくなるので注意が必要です。

それに眠い時とか条件式が入り混じってるときだと見つけるのに苦労するかも知れません。

出来ればデバッグに至る前にミスに気がついて、他人のコードと化す前に修正出来るようになりたいですね。

まとめ

今回は問9~問13について説明しました。

VBAにとっての常識、プログラミングにとっての常識、そして数学における常識。人によって腑に落ちる/納得の行かない文法はあるかと思いますが、こればっかりはなんともならないので、人間さんのほうが言語の方に歩み寄ってあげてくださいね!

以上


何か御座いましたらコメント欄、またはTwitterからどうぞ♪

それでは、また今度♪ ちゅんちゅん(・8・)

次の解説

問14~問16の解説 Dictionaryの注意事項

kotori-chunchun.hatenablog.com