何となく Blog by Jitta
Microsoft .NET 考

目次

Blog 利用状況
  • 投稿数 - 591
  • 記事 - 18
  • コメント - 2182
  • トラックバック - 183
ニュース
  • 検索エンジンで来られた方へ:
    お望みの情報は見つかりましたか? よろしければ、コメント欄にどのような情報を探していたのか、ご記入ください。
It's ME!
  • はなおか じった
  • 世界遺産の近くに住んでます。
  • Microsoft MVP for Visual Developer ASP/ASP.NET 10, 2004 - 9, 2009
サイト内検索
広告

記事カテゴリ

書庫

日記カテゴリ

ギャラリ

その他

わんくま同盟

同郷

 

参照型については刈歩 菜良さんに振ったので、参照渡しと値渡しを行きましょう。


ByRef, ByVal で渡したプロシージャ内で、変数そのものを書き換えた場合、ByRef と ByVal がどのような結果を返すか、です。実行結果はどのようになるでしょうか。

その前にですね、この前のコードを、C++ で書いてみます。C++/CLI でも Managed C++ でもない、C++ です。Visual Studio 2003 のプロジェクトの追加で、「C++ コンソール アプリケーション」を選択しました。

#include "stdafx.h"
#include <iostream>

class HogeClass {
public:
    int Value;
    HogeClass() {
        Value = 0;
    };
};
void ByValFunc(HogeClass hage);
void ByRefFunc(HogeClass* hage);

int _tmain(int argc, _TCHAR* argv[])
{
    HogeClass hoge1;

    hoge1.Value = 1;
    std::cout << hoge1.Value;  // [A]

    ByValFunc1(hoge1);
    std::cout << hoge1.Value;  // [B]

    ByRefFunc1(&hoge1);
    std::cout << hoge1.Value;  // [C]
}

void ByValFunc1(HogeClass hage)
{
    hage.Value = 2;
    //hage = new HogeClass();    // [イ]
    //hage.Value = 3;
}

void ByRefFunc1(HogeClass* hage)
{
    hage->Value = 4;
    hage = new HogeClass();
    hage->Value = 5;
}

なんと、[イ]のところで、コンパイル エラーが出ます。ということで、“全く同じ”ではないのですが、実行してみます。

結果は、114 です。

やっとスタート地点だよ。。。

「値渡し」は、引数「の値」を渡します。この場合、新しい HogeClass の為にメモリが確保され、全く同じ内容になるようにコピーされたものが、ByValFunc の hage になります。
hage は hoge のコピーなので、ByValFunc 関数内で変更した内容は、ByValFunc で宣言された hage にのみ適用され、_tmain に伝わることはありません。このため、ByValFunc 関数の中での変更は反映されず、[B] では "1" が出力されます。

「参照渡し」は、引数「への参照」を渡します。この場合、新しい HogeClass の為のメモリは確保されず、全く同じ場所を参照するものが、ByRefFunc の hage になります。
hage は hoge と同じ場所を指すので、ByRefFunc 関数内で変更した内容は、_tmain で宣言された hoge と同じ場所を変更するため、hoge の内容も変わります。つまり、ByRefFunc 関数の中での変更は、hoge を直接操作したのと同じとなり、[C] では "4" が出力されます。

では、なぜ[イ]の部分がコンパイル エラーになるのでしょうか。

C++ では、変数の宣言時に、値を保持するもの、アドレスを保持するものとして宣言できます。HogeClass hage; という宣言では、HogeClass という入れ物の、値を保持する変数と宣言します。しかし、new HogeClass(); は、HogeClass という入れ物を保持するために用意した場所を指す値を返します。
入れ物と、入れ物のある場所。
言い換えれば、「コーヒーカップ」と、「コーヒーカップは戸棚にあるよ」。この2つに互換性はありません。互換性がないことをコンパイル時に検出し、エラーとします。

ざっくりと。変数宣言に "*" が付いていたら、「場所」を指す。付いていなければ「そのもの」を指す。こんな感じで。C 言語で必ず躓くポイントなので、さらっと次に進む。


さて、今回のエントリの C++ によるコードと、前回のエントリの VB7.0 によるコードを比べてみましょう。
まず、結果を比べます。おっと、VB7.0 での結果をまだ書いてなかったですね。VB7.0 では "125" となります。VB7.0 のコードをもう一度書きますね。

Public Class HogeClass
    Public Value As Integer
End Class

Private Sub ByValAndByRef(ByVal sender As Object, ByVal e As EventArgs)
    RemoveHandler Application.Idle, AddressOf ByValAndByRef
    Dim hoge As New HogeClass

    hoge.Value = 1
    TextBox1.Text = hoge.Value.ToString()    ' [A] 1 を表示

    ByValFunc(hoge)
    TextBox1.Text += hoge.Value.ToString()   ' [B] 2 を表示

    ByRefFunc(hoge)
    TextBox1.Text += hoge.Value.ToString()   ' [C] 5 を表示
End Sub

Sub ByValFunc(ByVal hage As HogeClass)
    hage.Value = 2         ' [B] で表示される
    hage = New HogeClass   ' [あ]
    hage.Value = 3
End Sub

Sub ByRefFunc(ByRef hage As HogeClass)
    hage.Value = 4
    hage = New HogeClass   ' [い]
    hage.Value = 5         ' [C] で表示される
End Sub

ここで注目なのは、変数 hoge を宣言しているところにある、New というキーワードです。C++ のコードには、 _tmain 関数での宣言にこのキーワードがありませんでした。

ということで、C++ のコードにこのキーワードを入れようとすると、あっちこっちでコンパイル エラーが出るようになりますorz

そして、コンパイル エラーをとって、VB7.0 のコードと等しい動きをするようになったのが、次のコード。

#include "stdafx.h"
#include <iostream>

class HogeClass {
public:
    int Value;
    HogeClass() {
        Value = 0;
    };
};

void ByValFunc2(HogeClass* hage);
void ByRefFunc2(HogeClass** hage);

int _tmain(int argc, _TCHAR* argv[])
{
    HogeClass* hoge2;
    hoge2 = new HogeClass();

    hoge2->Value = 1;
    std::cout << hoge2->Value;

    ByValFunc2(hoge2);
    std::cout << hoge2->Value;

    ByRefFunc2(&hoge2);
    std::cout << hoge2->Value;
}

void ByValFunc2(HogeClass* hage)
{
    hage->Value = 2;
    hage = new HogeClass();
    hage->Value = 3;
}

void ByRefFunc2(HogeClass** hage)
{
    (*hage)->Value = 4;
    *hage = new HogeClass();
    (*hage)->Value = 5;
}

まず、hoge2 の宣言が、"*" 付き、すなわち「場所」を指すように変わりました。

ここです。

VB7.0 では、というより、共通型システム(CTS)では、ですね。CTS では、System.Object を継承した型はすべて、参照型となります。つまり、「場所」を指すようになっています。ここ、突っ込むと刈歩さんのセッションとバッティングするので、これくらいにしておく。

ByVal で引き渡す場合、引き渡すのは変数の値なのですが、その「値」とはつまり、「参照」なのです。「この変数の実体は、この場所にあるよ」という場所への参照情報を値渡しするので、引き渡された関数からでも、引数の内容を変更することができるのです。

そして、C++ のコードと見比べていただきたいのですが。あっと。ここには VB7.0 でのコードを載せましたが、C# でもそう変わらないコード内容になります。気をつけるのは、ByRef が、ref になることくらいでしょうか。

さて、強調してあるところが違うところだと、気づいていただけたかと思います。気づいて欲しいから強調しているわけですが。

hoge2 の宣言に "*" がついて、場所を指すようになったのと同様、ByValFunc, ByRefFunc の引数にも "*" がついて、ByRefFunc なんか2つも付いちゃってます。引き渡すところも、"&" が付いていたりしています。つまり、こうやって変数の内容を、値そのものなのか、値が格納してある場所なのか、人が指定しているわけです。ややこしいですね。

VB7.0 や C# では、このようなややこしいことを、言語仕様によって人が指定しなくていいようにしてあります。便利ですね。


え?なに?
「どうして、わざわざ int 型を持つクラスを定義するの?int 型を直にやればいいんじゃない?」
ですって?その辺は、刈歩さんがたっぷり説明してくださるでしょう。そうすると…

投稿日時 : 2007年5月31日 22:28
コメント
  • # re: VB マイグレーション ByRef と ByVal - その2
    とっちゃん
    Posted @ 2007/05/31 22:38
    void ByRefFunc2( HogeClass** hage )
    ではなく、
    void ByRefFunc2( HogeClass*& hage )
    とすると、本当に参照型になります。


    C++のですけどw

    そうすると、関数の中身は、ByValFunc2 と同じ書き方になります。
    もちろん結果は異なりますw

    さらに難しいところですね。ポインタを分かっていても C->C++ への移行ではまる個所の一つですw
  • # re: VB マイグレーション ByRef と ByVal - その2
    Jitta
    Posted @ 2007/06/02 8:19
    とっちゃんさん、コメントありがとうございます。

    > もちろん結果は異なりますw
    いや、それじゃ、話し続けられませんがな(^-^;
  • # re: VB マイグレーション ByRef と ByVal - その2
    とっちゃん
    Posted @ 2007/06/02 13:15
    あ、すまん。日本語が足りないw
    ByValFunc2 の中身と同じ書き方だけど、ByValFunc2 とは結果が異なる。です。

    C# の参照渡しと同じ結果になりますよw

    だもんで、わけわからん...orzとなりやすいw
  • # re: VB マイグレーション ByRef と ByVal - その2
    刈歩 菜良
    Posted @ 2007/06/06 12:53
    ほんとはメソッド呼び出し時の値型・参照型受け渡しのイメージ図まで行く予定やってんけど、時間足りまへんでした。
    なので、前3~4回コースとなって、年内にはrefの話に行けるかなぁ。って感じでしょうか。
    (^^ゞ
  • # re: VB マイグレーション ByRef と ByVal - その2
    Jitta
    Posted @ 2007/06/08 7:56
    うぉ~ん。゜(>д<)゜。
    なんでメモリー図がないんだよぉ~。゜(>д<)゜。
  • # re: VB マイグレーション ByRef と ByVal - その2
    刈歩 菜良
    Posted @ 2007/06/08 12:26
    高橋メソッドだから
    (ё_ё)キャハ
タイトル  
名前  
Url
コメント