[[20180918114500]] 『VBA 値渡し参照渡しについて』(T18) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『VBA 値渡し参照渡しについて』(T18)

お世話になります。
質問)
下記コード(※)で「A1セルが1」になる理由がわかりません。
https://excel-ubara.com/excelvba4/EXCEL218.html からの抜粋です

サイトでは「当然ですよね」といっていますが、私のオツムでは「元のA1の値」が答えです・・
値渡しは「呼出し元に変更は加えない」がオブジェクトが対象になるとそうではないよ、が主旨のようですが、
いきなり「当然」、といわれても・・という思いです。
その後に別コードで違いを説明されていますが、件のコードの理解にはいたらず(たまにわかった気がする時もあり)悶々としています。

いろいろ調べてはいるのですが、どうもスキっとしません…
ご教授お願いします。

 Sub sample()
  Dim MyRange As Range
  Set MyRange = Range("A1")
  Call sample2(MyRange)
 End Sub
 Sub sample2(ByVal MyRange As Range)
  MyRange.Value = 1
 End Sub

< 使用 Excel:Excel2010、使用 OS:Windows10 >


Rangeを引数として渡した場合、ByRefを使おうがByValを使おうが
 Range("A1").Value=1

を意味しています。

そこでツマル人は居ないだろうと思って「当然」という言葉を使ったのだと思います。
その後の部分は混乱しそうですけどね。
(名無し) 2018/09/18(火) 12:02


 これだとわかる?

 Sub sample()  'これを実行
    Dim MyRange As Range
    Set MyRange = Range("A1")

    Call sample2(MyRange)
    MsgBox MyRange.Address(0, 0) & " 2回目"

    Call sample3(MyRange)
    MsgBox MyRange.Address(0, 0) & " 4回目"
 End Sub

 Sub sample2(ByVal MyRange As Range)
    MsgBox MyRange.Address(0, 0) & " 1回目"
    Set MyRange = Range("A2")
 End Sub

 Sub sample3(ByRef MyRange As Range)
    MsgBox MyRange.Address(0, 0) & " 3回目"
    Set MyRange = Range("A2")
 End Sub
(BJ) 2018/09/18(火) 13:02

 もっと混乱するかも。
 変数の型とValueプロパティ。

 Sub sample4()
    Dim MyRange As Variant
    Range("A1").Value = 10000

    MyRange = Range("A1").value
    Call sample5(MyRange)

    Set MyRange = Range("A1")

    Call sample6(MyRange)
    MsgBox Range("A1").Value

    Call sample7(MyRange)
    MsgBox Range("A1").Value
 End Sub

 Sub sample5(ByVal MyRange As Variant)
    MsgBox MyRange
 End Sub

 Sub sample6(ByVal MyRange As Variant)
    MyRange = 12345
 End Sub

 Sub sample7(ByVal MyRange As Variant)
    MyRange.Value = 99999
 End Sub

(BJ) 2018/09/18(火) 13:24


 >値渡しは「呼出し元に変更は加えない」

 呼出元とは、sampleのMyRange自体です。それには何の影響も与えない、と言う意味です。
 つまり、そのValueプロパティについては何も言ってないです。

 sample2の仮引数であるMyRangeも、Range("A1")オブジェクトを参照しています。
 なので、byValであろうが、byRefであろうが、そのValueプロパティを変更したらA1セルの値は変化します。

 影響を与えないと言うのは、例えば
 sample2の中で、MyRangeにRange("A2")を代入し直したり、Set Nothingにしても、
 sampleのMyRangeの方は、引き続きRange("A1")を参照したままである、ということです。

(半平太) 2018/09/18(火) 13:31


良いところを疑問に感じたと思います。 C言語のポインタに相当する考え方ですね。

元サンプル5個について説明を追加すると、以下のようになります。 ByValは単一変数なら値ですが、オブジェクトや配列の場合は、値がいっぱいあるので、先頭アドレスだけが渡ります。 そして、ByRefを指定すると、そのアドレスが渡るようになります。 ここが理解できれば納得できるようになるはず。

(1)引数のデフォルトは参照渡し。だから、関数側で値を変えると、呼び出し側でも変わる。(値のアドレスが渡る)
(2)ByVal を付けると、値渡しになる。関数側で値を変えても、呼び出し側の変数は変化しない。(値が渡る)
(3)例えばRangeオブジェクトを値渡しした場合は、オブジェクトのアドレスが渡るので、そのアドレス先を変えると元も変わる。(オブジェクトのアドレスが渡る)事前に Set MyRange = Range("A1") を実行しているので、MyRangeを変えるとRane("A1")も変わる。
(4)オブジェクトにByValを付けた場合は、呼び出し側のMyRangeと関数のMyRangeは対象を合わせただけであり、実体は別物。(オブジェクトのアドレスが渡る)
(5)オブジェクトにByRefを付けた場合、オブジェクトのアドレスのアドレスが渡る。だから5つ目のサンプルでは、オブジェクトの参照先を変えた事が呼び出し元にも伝わっている。

4つ目、5つ目のサンプルに、以下のようにアドレス表示を追加して、ByValとByRefの違いを確認してみてください。

 Sub sample()
    Dim MyRange As Range
    Set MyRange = Range("A1")
    MsgBox VarPtr(MyRange)
    Call sample2(MyRange)
    MyRange.Value = 2
 End Sub
 Sub sample2(MyRange As Range)
    MyRange.Value = 1
    Set MyRange = Range("A2")
    MsgBox VarPtr(MyRange)
 End Sub
(???) 2018/09/18(火) 13:45

名無しさん、BJさん、半平太さん、???さん
早々にありがとうございます。

 >変数の型とValueプロパティ
 >・・Valueプロパティについては何も言ってない・・
 >ByValは(オブジェクトや配列の場合は)先頭アドレスだけが渡り・・ByRefを指定すると、
 そのアドレスが渡る・・

上記とコード確認でかなり理解が進んだ気がします。

アドレス(メモリの動作?)のことをもっと知る必要があると感じました。
(≒“C言語のポインタに相当する考え方”に通じること??)
あと、オブジェクト、プロパティ...(まだまだです)

これからもよろしくお願いします。
(T18) 2018/09/18(火) 14:56


 ???さん のコメントは本当に分かったんですか?

 私にはチンプンカンプンでしたけど。

(半平太) 2018/09/18(火) 15:26


 >変数の型とValueプロパティ
 >・・Valueプロパティについては何も言ってない・・

 なんと言ったらいいのやら・・・・。
 単純に変数の型を使用にあった型に合わせないで、Valueプロパティがある場合と無い場合の挙動の違いを体験させたかっただけなんだけど・・・。
 (Dim MyRange As Range と Dim MyRange As Variant)
 別にValueプロパティの講釈たれたわけじゃないんだけど。

 単純に
 ByVal は、引数の中身を変えて戻せない。
 ByRef は、引数の中身を変えて戻せる。(答えなどを入れて)
 ってな感じで覚えちゃったけど。(引数の型は、同じ)
(BJ) 2018/09/18(火) 15:51

今「https://thom.hateblo.jp/entry/2016/10/06/202616」なぞでアドレスのことを学習中です…

「呼出元とは、sampleのMyRange自体」で 「Range("A1").Value=1を意味・・」をようやく
理解できるレベルなので、まだ道程は長いです。

(T18) 2018/09/18(火) 16:39


覚えるのは、ByValで値渡しすると関数側で変えても呼び出し側は変わらない、という点だけでしょうね。
なんでそうなるの?、と突っ込んだとき、以下2点が出てきます。
・値渡しとアドレス渡しの違い。
・配列やオブジェクトは通常でも先頭アドレスで表現されている。

元サンプルで混乱するのは、Valueを使っているところですねぇ。 これがあるせいで、値で渡しているのに変わったよ?、と見えちゃう。

そして、最近の言語はアドレスなんて意識しないようになっているし、VBだって元々アドレスを意識するのはC言語用に作られたAPIインターフェースの互換性のためだし、原理だけなんとなく理解する程度で十分かと思います。(C言語の頃は、アドレス渡ししてしまうと関数のバグのせいで呼び元の変数やコードが壊される可能性があるので、なるべく値で渡せ、って感じでしたが、VBならそういう事はないので、デフォルトのアドレス渡しだけ使っとけ、なんだろうと思っています)

ByValなんて覚えなくても良いよ、というのが私の感想かな。 ただ、そこを疑問に思ったところは、プログラム関係の素質があるように思います。
(???) 2018/09/18(火) 16:51


 ちょっと趣旨からは逸脱するお話でスミマセン。

 >ByValなんて覚えなくても良いよ
 結局行き着く処はそうなのかも知れませんが、
 私の様な「うっかり者」は、これでイタイ目にあったりしました^^;

 個人的に「やってしまいがち」なのが、
 Optionalキーワード付きで渡された引数の場合ですね。
 処理の先頭に、Optionalな引数の例外処理を入れ込んだりすることが多いと思うんですが、
 (例えば引数がマイナス値だったらゼロに補正とか)
 この補正を引数に対して直接やってしまうんですよね。うっかり。

 目的が無いのなら、引数の内容を直接書き換える処理はすべきではない。
 ってのが徹底できていれば問題ないのですが、
 ついつい気付かない内にやっちゃうんですよね^^;
 (何でOptional付けた途端、そこが疎かになってしまうのか、自分でも謎)

 私の場合、どうにもこの点が徹底出来ないので、最初に、
 引数の内容を「変化させて返すのが目的」なのか「見るだけ」なのかを考えて、
 「見るだけ(のつもり)」なら、予防措置としてByValを付けておくようにしてます。

(白茶) 2018/09/18(火) 19:43


 >ByValなんて覚えなくても良いよ、

 これも理解できないなぁー
 イベントプロシージャの仮引数を見ると、大抵 ByVal でお仕着せになっています。

 元オブジェクトに影響させたくなければ、ByVal X
 元オブジェクトに影響させたければ   ByRef X

 と言うしかないと思いますけどねぇ。

 オブジェクトだから、そのプロパティを操作すれば、同じ影響が出ます。
 そこが当然の如く分からないと、始まらないなぁって気がします。

 Set X = Nothing 
 Set X = 別オブジェクト
  
 そんなことをやった時に、元のオブジェクト変数に影響させたいか、させたくないか。

 そこだけの違いです。(上で一度書いた通りですけど)

(半平太) 2018/09/18(火) 20:04


 値渡しと参照渡しの話がオブジェクトについてなら、解説しているページがあります

https://www.relief.jp/docs/excel-vba-byval-and-byref-of-object.html

(2u) 2018/09/18(火) 22:17


 >オブジェクトだからそのプロパティを操作すれば同じ影響が出..そこが当然の如く分からないと始まらない..

ようやく上記の状態からは脱せたかな、と思えるようになったと感じます。
2uさんご紹介サイトや私がUPしたサイトで???さんの「先頭アドレス」の意味は
わかった気がしています。
(ただ、(1)〜(5)のところは正直わかってません..スミマセン)
ByValが原則、みたいなサイトが多い中で「ByValなんて覚えなくても良い」とのコメントは新鮮でした。(私には)

私はByRefしか使用実績がありませんが深い考えがあるわけではなく、みなさんのレスはいい勉強になりました。
ありがとうございます。

これからもよろしくお願いします。
(T18) 2018/09/19(水) 12:53


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.