はじめに
皆さんは以下のようにほぼ同じコードを書いているのにも関わらず、
構造体ではエラーが起きて、クラスではエラーが起きない理由を説明することができますか。
構造体(値型)
struct Sample {
var num: Int
}
var sample1 = Sample(num: 1)
let sample2 = Sample(num: 1)
sample1.num = 2
sample2.num = 2 //=> エラー
クラス(参照型)
class Sample {
var num: Int
init(num: Int) {
self.num = num
}
}
var sample1 = Sample(num: 1)
let sample2 = Sample(num: 1)
sample1.num = 2
sample2.num = 2
自分はできませんでした。ところがクラスは参照型、構造体は値型ということがきちんと理解できればこの理由は説明することができ、クラスと構造体に対する理解も深まると思います。
値型の場合
値型の場合、今回だとnumという変数を格納するための入れ物がまず作られます。そしてその入れ物に直接値を格納していきます。
sample2はletで定義されているので、入れ物の中身を後から変えることはできません。
入れ物を作って値を格納する。初心者としてはイメージ通りの挙動だと感じると思います。しかし参照型はこれとはちょっと違った方法で値をセットします。
参照型の場合
参照型でも値型と同様に入れ物が作られます。しかし入れ物に入れる物が値そのものではなく、別の場所に格納された値の住所を入れます。参照型は、その住所を使って値にアクセスをします。ここが値型と参照型の大きな違いで、
let sample2 = Sample(num: 1) //参照型
sample2.num = 2
がエラーを起こさない理由は、入れ物に入った住所を書き換えるのではなく、住所の先にある値を変えているからです。
住所の先の値を変える分には、sample2がletで定義されていようが関係なく変更することができます。
参照型のいい点
let sample1 = Sample(a: 1, b: 2, c: 5, d: 1, .....)
let sample2 = sample1
上記のようにプロパティの数が膨大な場合に、sample1からsample2を複製しようとしたとき、値型では入れ物から中身に値まですべてコピーします。これではコンピュータの容量もたくさん食うことになります。
その点参照型ではコピーするのは住所だけとなっているので、容量も食わずにコンピュータにも優しいです。
let sample1 = Sample(a: 1, b: 2, c: 5, d: 1, .....)
let sample2 = sample1
sample1.b = 100
print(sample2.b) //=> 100
//住所の先の値を変えてしまっているので、sample1と同じ住所を持つsample2の値も変化する
また参照型で複製をしているときに、値を変更してしまうと大本の値を変更していることになり、他の要素にも影響を与えます。ここは嬉しい点でもあり、知らずに使ってしまうと混乱の原因にもなるので気をつけましょう。
まとめ
値型と参照型の違いを解説してみました。間違い等ありましたら知らせていただけると嬉しいです。