#はじめに
C#を勉強し始めて1年以上経過したので自分の備忘録としての投稿。
実際にコードを書いていくうちに値型と参照型、値渡しと参照渡しの違いをちゃんと知っておくことの大切さを知ったので記事にしました。
間違いなどありましたらコメント欄にてご指摘いただけると幸いです。
#目次
1.変数の種類(値型と参照型)
2.値渡しと参照渡し
3.まとめ
#1.変数の種類(値型と参照型)
よくある参考書では__「変数とは箱のようなものである」__と説明されていますが、
実はその覚え方あまりよくないかもしれません。
変数には大きく分けて__値型__と__参照型__の2種類があり、それぞれ値の持ち方が違います。
どのように違うかを実際のソースコードと図を交えながら解説していきます。
例えば以下のように変数に値を代入した場合を考えてみます。
int x = 12;
string y = "apple";
int型の変数xには12、string型の変数yには"apple"を代入しています。
「 変数 = 箱 」と考えている人の中には以下のようなイメージを持っている人が多いかもしれません。
どちらも__変数(箱)__に値を入れているのだからあってるじゃんと思うかもしれません。
しかし実際の変数の中身は以下のようになります。
int型の場合は変数xという箱の中に12という値が入っているのでここはイメージ通りかと思います。
問題は「string型__の__変数y」の方です。
int型の変数xとは違い、string型の変数yという箱の中には文字列の"apple"そのものではなく実際に"apple"が置かれている場所の__アドレス__が入っています。
__「置かれている場所のアドレス・・・? なにそれ??」__という声が聞こえてきそうですが
ひとまずは箱の中に入っているのは"apple"という文字列そのものではなくアドレスなんだなぁくらいの認識でOKです。
実はこれが冒頭に出てきた__値型__と__参照型__の__値の持ち方の違い__なんです。
メモリには__スタック__と__ヒープ__という2種類の領域があります。
##値型
値型の変数はスタックに領域が確保され、確保された領域内に値が置かれます。
(上の例ではint型の変数xが使用する領域がスタック上に確保され、確保された領域に12が置かれます。)
##参照型
参照型の変数はスタックに領域が確保されますが、値そのものはヒープ上に置かれます。
スタック上に確保された領域にはヒープ上にある値の場所を示す__アドレス(ヒープ上にある値の場所を表すもの)__が置かれます。
(上の例ではstring型の変数yが使用する領域がスタックに確保され、確保された領域にはヒープ上に置かれた"apple"の場所を表すアドレス値が置かれます。)
#2.値渡しと参照渡し
変数には値型と参照型があるということを前項で説明しました。
この項ではメソッドへの引数の渡し方について説明していきます。
説明中に実引数と仮引数という言葉が出てきますので、うろ覚えの方はここで覚えておいてください。
###実引数
メソッドを呼び出す際に実際に渡す値の事を__実引数__と言います。
int x = 12;
string y = "apple";
SampleMethod(x, y); // xとyは実引数
###仮引数
メソッドを呼び出す際に値を受け取る変数の事を__仮引数__と言います。
void SampleMethod(int x, string y) // xとyは仮引数
{
// 引数を用いた何らかの処理
}
##値渡し
仮引数に対して、実引数に指定したの値のコピーを渡す事を値渡しと言います。
仮引数の値を変更しても実引数に指定した変数には影響がありません。
static void Main()
{
// 呼び出し元
SampleMethod(x, y)
}
void SampleMethod(int x, string y)
{
// 引数を用いた何らかの処理
}
##参照渡し
仮引数に対して、実引数に指定した値が格納されている場所(アドレス)を渡すことを参照渡しと言います。
仮引数に値を変更すると実引数に指定した変数にも変更の内容が反映されます。
(C#ではref/in/outを実引数・仮引数に追加すると参照渡しとなる)
static void Main()
{
// 呼び出し元
SampleMethod(ref x, ref y)
}
void SampleMethod(ref int x, ref string y) // 参照渡しでは仮引数の型指定の前にrefを追加する
{
// 引数を用いた何らかの処理
}
上の説明だけではイマイチわかりづらいと思うので以下にサンプルコードを提示します。
値渡し・参照渡しのイメージを掴んでみてください。
class Program
{
static void Main()
{
// クラスのインスタンスを生成(参照型)
SampleClass samClass1 = new SampleClass();
RefVal(samClass1);
System.Console.WriteLine("samClass1.x = " + samClass1.x); // 結果:samClass1.x = 100
SampleClass samClass2 = new SampleClass();
RefRef(ref samClass2);
System.Console.WriteLine("samClass2.x = " + samClass2.x); // 結果:samClass2.x = 100
// 構造体のインスタンスを生成(値型)
SampleStruct samStruct1 = new SampleStruct();
ValVal(samStruct1);
System.Console.WriteLine("samStruct1.x = " + samStruct1.x); // 結果:samStruct1.x = 0
SampleStruct samStruct2 = new SampleStruct();
ValRef(ref samStruct2);
System.Console.WriteLine("samStruct2.x = " + samStruct2.x); // 結果:samStruct2.x = 100
}
// 参照型の値渡し
static void RefVal(SampleClass sam)
{
sam.x = 100;
}
// 参照型の参照渡し
static void RefRef(ref SampleClass sam)
{
sam.x = 100;
}
// 値型の値渡し
static void ValVal(SampleStruct sam)
{
sam.x = 100;
}
// 値型の参照渡し
static void ValRef(ref SampleStruct sam)
{
sam.x = 100;
}
}
// クラス(参照型)
class SampleClass
{
public int x;
}
// 構造体(値型)
struct SampleStruct
{
public int x;
}
上のソースについてそれぞれ解説していきます。
###参照型の値渡し
まず参照型の値渡しですが、上でお話したことを思い出してみてください。
それぞれ以下のようになります。
参照型:変数には__アドレス__を保持している
値渡し:変数の__値のコピー__を渡す
つまり、アドレスのコピーを渡しているのでsamClass1とsamはともに同じアドレスを格納しているということです。
アドレスが同じなのでsamが参照しているオブジェクトに対して行った変更はsamClass1にも反映されます。
(同じものを参照しているのですから、冷静に考えてみると当たり前ですね(笑))
// コード抜粋
static void Main()
{
// クラスのインスタンスを生成(参照型)
SampleClass samClass1 = new SampleClass();
RefVal(samClass1);
System.Console.WriteLine("samClass1.x = " + samClass1.x); // 結果:samClass1.x = 100
}
// 参照型の値渡し
static void RefVal(SampleClass sam)
{
sam.x = 100; // samとsamClass1の変数の中身はともに同じアドレスが格納されている
}
###参照型の参照渡し
参照型の参照渡しについて
それぞれ以下のようになります。
参照型 :変数には__アドレス__を保持している
参照渡し:変数の__場所(アドレス)__を渡す
つまり、オブジェクトの場所(アドレス)を保持している変数の場所(アドレス)を渡しているということです。
以下の画像のような経路でオブジェクトを参照することでxの値を変更しています。
// コード抜粋
static void Main()
{
// クラスのインスタンスを生成(参照型)
SampleClass samClass2 = new SampleClass();
RefRef(ref samClass2);
System.Console.WriteLine("samClass2.x = " + samClass2.x); // 結果:samClass2.x = 100
}
// 参照型の参照渡し
static void RefRef(ref SampleClass sam)
{
sam.x = 100;
}
###値型の値渡し
値型の値渡しについて
それぞれ以下のようになります。
値型 :変数には__値__を保持している
値渡し:変数の__値のコピー__を渡す
つまり、samとsamStructは別物なので、samのxを100に変更してもsamStruct1のxは変更されません。
// コード抜粋
static void Main()
{
// 構造体のインスタンスを生成(値型)
SampleStruct samStruct1 = new SampleStruct();
ValVal(samStruct1);
System.Console.WriteLine("samStruct1.x = " + samStruct1.x); // samStruct1.x = 0
}
// 値型の値渡し
static void ValVal(SampleStruct sam)
{
sam.x = 100;
}
###値型の参照渡し
値型の参照渡しについて
それぞれ以下のようになります。
値型 :変数には__値__を保持している
参照渡し:変数の__場所(アドレス)__を渡す
つまり、値を保持している変数の場所(アドレス)を渡しているということです。
以下の画像のような経路でxの値を変更しています。
// コード抜粋
static void Main()
{
// 構造体のインスタンスを生成(値型)
SampleStruct samStruct2 = new SampleStruct();
ValRef(ref samStruct2);
System.Console.WriteLine("samStruct2.x = " + samStruct2.x); // samStruct2.x = 100
}
// 値型の参照渡し
static void ValRef(ref SampleStruct sam)
{
sam.x = 100;
}
#3.まとめ
いかがだったでしょうか?
値型と参照型の違い、値渡しと参照渡しの違いをおわかりいただけましたでしょうか。
私自身が学生時代に「変数は箱」というイメージから脱却できず、C言語のポインタで躓いてしまったので、
少しでもこういった方の一助となればと思い投稿しました。
この記事が少しでもお役に立てていれば幸いです。