佐々木屋

技術的なことから趣味まで色々書きます

値型と参照型

プログラミングを学習すると必ずやらなければならない、そしてよく分からない?値型と参照型の違いについて。

このブログは初学者向けの教本では無く、初学者に説明した中でよく分かってもらえない・理解してもらえない分野のみを抜粋して記録するためのもので、このような基本的な話をするつもりはありませんでしたが、意外と分かっていない(分かった風になっている)のと、string型の話でどうしても連続性を持たせたかったので、簡単に説明してみようと思います。


C#VB.NET)の型は「値型」と「参照型」の二つに分類されます。おなじみのint型やstring型、クラス、構造体、デリゲートなどもこのどちらかの型に分類されます。

値型 string型以外のプリミティブ型
(int、double、decimal、date、boolなど)

スタック領域を使用するので動作は軽快。また間接的な参照が発生しないので、アクセスも高速に行われる。
その反面、サイズが大きいとコストがかかる。
構造体
列挙型
Null許容型
参照型 オブジェクト型
ヒープ領域を使用するので値型よりは動作が遅い。また間接的な参照が発生するので、アクセスも値型に比べるとコストがかかる。
複製は行われない(参照先の複製のみ)ので、サイズの大小に関わらず代入は高速に行われる。
配列
string型
クラス
デリゲート


値型

値型は変数に値を直接保存します。必ず変数と値が1対1の関係になるのが特徴です。

int int1 = 100;
int int2 = 200;
Dim int1 As Integer = 100
Dim int2 As Integer = 200

以下のようなイメージです。それぞれの変数ボックスに値が入っています。
f:id:sasaki816:20190105210222j:plain

当然、変数に値を代入すれば直接値が変更されますので、以下のようにしても結果は直感的に分かると思います。算数と一緒ですね。

int int1 = 100;
int int2 = int1;
Console.WriteLine(int2);

int2 = 200;
Console.WriteLine(int1);
Console.WriteLine(int2);
Dim int1 As Integer = 100
Dim int2 As Integer = int1
Console.WriteLine(int2)

int2 = 200
Console.WriteLine(int1)
Console.WriteLine(int2)
100
100
200

値型は特に難しいことはないと思います。


参照型

問題は参照型です。これは変数と値が1対1の関係にはなりません。というのも、変数には直接値が保存されるのではなく、別の場所に値が保存され、その宛先(参照先)が変数に保存されます。ここで大事なことは、値型とは違い、「new」をすることです。これによって新しい保存先が作成されます。よって、newしないで以下のようなコードを書くと、変数と値の参照がn対1の関係になります。

List<int> lst1 = new List<int>() { 100, 200 };
List<int> lst2 = lst1;
Dim lst1 As List(Of Integer) = New List(Of Integer) From {100, 200}
Dim lst2 As List(Of Integer) = lst1

つまり、lst2はnewせずlst1を代入していますので、lst1に保存されている宛先(ここではxxx)がlst2に代入される形です。
f:id:sasaki816:20190105220635j:plain

ここで変数lst2対して変更を加えてみましょう。

List<int> lst1 = new List<int>() { 100, 200 };
List<int> lst2 = lst1;
lst2.Add(300);
Console.WriteLine(string.Join(",", lst1));
Console.WriteLine(string.Join(",", lst2));
Dim lst1 As List(Of Integer) = New List(Of Integer) From {100, 200}
Dim lst2 As List(Of Integer) = lst1
lst2.Add(300)
Console.WriteLine(String.Join(",", lst1))
Console.WriteLine(String.Join(",", lst2))

lst2に対して300を追加しました。lst2は100、200、300の3つ値を保持しているのは簡単に分かると思います。では、lst1はどうなるでしょうか。lst2と同じ結果になると思った方はこの先読む必要はありません。

100,200,300
100,200,300

図で表すと以下の通りです。
f:id:sasaki816:20190106085109j:plain
lst1もlst2も参照型なので値は保持していません。あくまで宛先(ここではxxx)を保持しているにすぎないのです。よって、lst2に対して変化を加えると、同じ宛先を保持しているlst1も影響を受けるということになります。


では、以下の場合はどうでしょうか。

List<int> lst1 = new List<int>() { 100, 200 };
List<int> lst2 = lst1;
lst2 = new List<int>() { 100, 200, 300 };
Console.WriteLine(string.Join(",", lst1));
Console.WriteLine(string.Join(",", lst2));
Dim lst1 As List(Of Integer) = New List(Of Integer) From {100, 200}
Dim lst2 As List(Of Integer) = lst1
lst2 = New List(Of Integer) From {100, 200, 300}
Console.WriteLine(String.Join(",", lst1))
Console.WriteLine(String.Join(",", lst2))

3行目でnewしていますので、新しい宛先(ここではyyy)が作成されていますので、lst2に変化が加わってもlst1は影響を受けません。
f:id:sasaki816:20190106120055j:plain


string型も参照型ですが、少し特殊なので別の機会にまとめます。