技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

struct【Go】

宣言

変数をまとめたもの、すなわち構造体を意味します。

type Vertex struct{
    X, Y int
}

func main(){
    v := Vertex{X:1, Y:2}
    // v := Vertex{1, 2}    // このようにも書ける
    v.X = 100
    fmt.Println(v)    // {100 2}
    fmt.Println(v.X, v.Y)    // 100 2
}

structのフィールド名は頭文字を大文字にしないと、外部パッケージからアクセスすることができません。


mapやスライスとは異なり、空の宣言をしてもnilにはならず、初期値(intは0、stringは"")が設定されます。

var v2 = Vertex{}
fmt.Println(v2)    // {0 0}

v3 := Vertex{}
fmt.Println(v3)    // {0 0}

また、ポインタを宣言する場合は次のようにします。

v4 := new(Vertex)
fmt.Println(*v4)    // {0 0}    // 番地の実態を参照

v5 := &Vertex{}
fmt.Println(*v5)    // {0 0}    // 番地の実態を参照

1行見ただけで、ポインタを参照することがわかるため、
&Vertex{}による宣言が使われることが多いようです。

関数による変更

mapやスライスと同様、関数を用いて要素を変更するには、値ではなくポインタを渡す必要があります。
値渡しの場合の例です。値渡しの場合、コピーが作成され、コピーに対して外部関数内部の処理が行われます。
引数として渡したstructと外部関数の中で処理されるstructのアドレスが、異なっているのがわかります。

type Vertex struct{
    X, Y int
}

func changeVertexItem(v Vertex) {    // 値渡し
    v.X = 100    // 値渡しなので、コピーされた実体に対して処理される。
    fmt.Println(&v.X, v)    // 0xc000078120 {100 2}  アドレスが異なる
}

func main(){
    v := Vertex{1, 2}    // 値による宣言
    changeVertexItem(v)
    fmt.Println(&v.X, v)    // 0xc000078060 {1 2}  アドレスが異なる
    
}


ポインタ渡しにすると引数として渡したstructの値が変わります。
外部関数と同じ物を参照しているからです。

type Vertex struct{
    X, Y int
}

func changeVertexItem2(v *Vertex) {    // ポインタ渡し
    v.X = 100
    fmt.Println(&v.X, *v)    // 0xc0000780f0 {100 2}  アドレスは同じ
}

func main(){
    v2 := &Vertex{1, 2}    // ポインタによる宣言
    changeVertexItem2(v2)
    fmt.Println(&v2.X, *v2)    // 0xc0000780f0 {100 2}  アドレスは同じ
    
}


ちなみに、ポインタ渡しの場合、structの中の要素(上記例でいうとv.Xまたはv.Y)のアドレスは同じですが、
structそのもの(上記例でいうとv)のアドレスは、外部関数とmain関数とで異なります。
それでも、中の要素が同じ箇所を見ているので、問題なく処理できるのです。