このページでは
VBAにおける引数の参照渡しと値渡しについて、サンプルプログラムを実行し結果を確認しながら、それぞれの働きと違いを理解します。
参照渡しと値渡し
説明
参照渡し
プロシジャーに、引数の値を渡すのではなく、引数のアドレスを渡す方法です。
これにより、プロシージャは、実際の変数にアクセス出来るため、値の変更も可能です。
また、実際の値の受け渡しがないので、値渡しよりも性能面において有利です。
VBAでは、指定がない限り、引数は参照渡しになります。
記述例)参照渡しの引数は、引数parmにByRefを記述します。
Sub procxxx(ByRef parm As Integer)
値渡し
プロシジャーに、引数の値をコピーして渡します。
これにより、プロシージャは、実際の変数にはアクセス出来ないため。値の変更は出来ません。
また、実際の値の受け渡し(コピー)があるので、参照渡しよりも性能面において不利です。
(「VBEの用語集」では、String型とVariant型は、通常は値渡しで渡さないよう記述があります。)
記述例)値渡しの引数は、引数parmにByValを記述します。
Sub procxxx(ByVal parm As Integer)
使い方
プロシジャーの処理結果を戻り値に格納する場合、1つならFucntionプロシジャーを利用すると思いますが、複数の戻り値を返したい場合には、Subプロシジャーの引数を参照渡しで定義するのもひとつの手です。
但し、呼び出し側では、常に引数の値が変化してしまうことに留意しておかないと、いつのまにか自分の変数領域が変更されてしまったためにバグの原因になってしまった、ということになりかねませんので注意が必要です。
実験室
実験室では、
・参照渡しの実験
・値渡しの実験
・指定なしの実験
をサンプルプログラムをまじえて実験します。
参照渡しの実験
参照渡しでは、引数の値そのものが渡るわけではなく、引数のアドレスが渡ります。
アドレスが渡る、とは何のことやら?と思う方も多いと思いますが、引数の存在する場所(引数のありか)が渡る、と考えるとわかりやすいかもしれません。
「引数は、ここにあるよー」って感じですね。
引数の存在する場所(引数のありか)を渡すということは、呼び出し側も呼び出された側も、同じ場所の値を参照することになります。
従って、引数として渡された値を、呼び出された側が変更すると、呼び出し側の値も変更されます。
(引数が同じ場所にあるので当然ですね。)
このサンプルプログラムでは、参照渡しの引数を持つプロシジャーを呼び出した後、呼び出した側の引数の値も変更されていることを確認します。
準備したサンプルプログラムのソース
Sub main01() Dim workint As Integer Dim workstr As String ' 参照渡しの呼び出し workint = 1 test01 workint Debug.Print "main01 ="; workint ' 参照渡しの呼び出し workstr = "AAAAA" test02 workstr Debug.Print "main01 ="; workstr End Sub ' ' 参照渡し Integer ' Sub test01(ByRef parm As Integer) Debug.Print "test01 前 ="; parm parm = 100 Debug.Print "test01 後 ="; parm End Sub ' ' 参照渡し String ' Sub test02(ByRef parm As String) Debug.Print "test02 前 ="; parm parm = "ZZZZZ" Debug.Print "test02 後 ="; parm End Sub
サンプルプログラムと実行結果
解説
[]内の数字は上記サンプルプログラムのライン番号です。
main01からtest01の呼び出し
・main01では、workintに1を設定してtest01を呼び出す。[7-8]
呼び出されたtest01の処理
・test01は、参照渡しで引数を受ける。[21]
・引数に100を設定する。[25]
※引数の前後の値をモニター[23, 27]
test01呼び出し後のmain01
・引数の値を参照すると、100に変わっている。[9]
※test02の呼び出し後も同様に値が変化しています。
値渡しの実験
値渡しでは、引数の値がコピーされて渡ります。
原本ではなくコピーが渡りますから、呼び出し側と呼び出された側は、異なる場所の値を参照することになります。
従って、引数として渡された値を、呼び出された側が変更しても、呼び出し側の値は変更されません。
(異なる場所にあるので当然ですね。)
このサンプルプログラムでは、値渡しの引数を持つプロシジャーを呼び出した後、呼び出した側の引数の値は変更されていないことを確認します。
準備したサンプルプログラムのソース
Sub main02() Dim workint As Integer Dim workstr As String ' 値渡しの呼び出し workint = 1 test01 workint Debug.Print "main02 ="; workint ' 値渡しの呼び出し workstr = "AAAAA" test02 workstr Debug.Print "main02 ="; workstr End Sub ' ' 値渡し Integer ' Sub test01(ByVal parm As Integer) Debug.Print "test01 前 ="; parm parm = 100 Debug.Print "test01 後 ="; parm End Sub ' ' 値渡し String ' Sub test02(ByVal parm As String) Debug.Print "test02 前 ="; parm parm = "ZZZZZ" Debug.Print "test02 後 ="; parm End Sub
サンプルプログラムと実行結果
解説
[]内の数字は上記サンプルプログラムのライン番号です。
main02からtest01の呼び出し
・main02では、workintに1を設定してtest01を呼び出す。[7-8]
呼び出されたtest01の処理
・test01は、値渡しで引数を受ける。[21]
・引数に100を設定する。[25]
※引数の前後の値をモニター[23, 27]
test01呼び出し後のmain02
・引数の値を参照すると、1のままです。[9]
※test02の呼び出し後も同様に値は変化していません。
指定なしの実験(参照渡し)
引数に、参照渡しや値渡しの指定をしない場合、参照渡しと解釈されて実行されます。
標準化規約が定められたプロジェクトのようなところで開発する場合は、参照渡しや値渡しの指定を必須とする、のようなルールがあるかもしれませんが、大抵の場合、指定なしでプログラミングしているのではないでしょうか。
指定なしでも構わないと思いますが、指定をしない場合、参照渡しと解釈される、ということを意識するようにしましょう。
このサンプルプログラムでは、渡し方法の指定なしのプロシジャーを呼び出したときに、参照渡しと同じ結果になることを確認します。
準備したサンプルプログラムのソース
Sub main03() Dim workint As Integer Dim workstr As String ' 値渡しの呼び出し workint = 1 test01 workint Debug.Print "main03 ="; workint ' 値渡しの呼び出し workstr = "AAAAA" test02 workstr Debug.Print "main03 ="; workstr End Sub ' ' デフォルト Integer ' Sub test01(parm As Integer) Debug.Print "test01 前 ="; parm parm = 100 Debug.Print "test01 後 ="; parm End Sub ' ' デフォルト String ' Sub test02(parm As String) Debug.Print "test02 前 ="; parm parm = "ZZZZZ" Debug.Print "test02 後 ="; parm End Sub
サンプルプログラムと実行結果
解説
[]内の数字は上記サンプルプログラムのライン番号です。
main03からtest01の呼び出し
・main03では、workintに1を設定してtest01を呼び出す。[7-8]
呼び出されたtest01の処理
・test01は、渡しの指定なしで引数を受ける。[21]
・引数に100を設定する。[25]
※引数の前後の値をモニター[23, 27]
test01呼び出し後のmain03
・引数の値を参照すると、100に変わっている。[9]
※test02の呼び出し後も同様に値が変化しています。
※この振る舞いは参照渡し(main01)の結果と同様です。
補足
参考:「VBEの用語集」
https://msdn.microsoft.com/ja-jp/library/office/gg264568.aspx