参照型については刈歩 菜良さんに振ったので、参照渡しと値渡しを行きましょう。
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