golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang) 構造体の定義と使い方

golang.png


構造体(struct)の定義 型名とフィールド名は先頭の文字を小文字にすると関数や変数と同じで、 パッケージとして import で呼び出した場合にアクセスできない(priveteになる)

type [型(構造体)の名前] struct {
    [フィールド名] [型名]
    [フィールド名] [型名]
    [フィールド名] [型名]
}

同じ型のフィールド名は、カンマ区切りで書くことができます。変数 var a, b, c string と同じ

type A struct {
    b, c, d string
    e       bool
}

空の構造体はサイズが 0

package main

import "unsafe"

func main() {
    s := struct{}{}
    size := unsafe.Sizeof(s)
    println(size) // 0
}





目次



基本的な構造体の使い方

構造体の定義と初期化

package main

// 型名(構造体の名前)を type の後に書く
type Profile struct {
    // フィールド名と型を記述する
    Name string
    Age  int
}

func main() {
    p := new(Profile)
    // フィールドに値をセット
    p.Name = "Tanaka"
    p.Age = 31
    // フィールドの内容は p.Name で取得できる
    println(p.Name, p.Age)


    // p2 と p3 は、上でやってることと同じ
    p2 := &Profile{
        Name: "Tanaka",
        Age:  31,
    }

    // 順番通りであればフィールド名は省略できる
    p3 := &Profile{"Tanaka", 31}



    // ポインタにしない場合は & をつけなければいい
    p4 := Profile{
        Name: "Tanaka",
        Age:  31,
    }
}


グローバルに定義する以外に、どこかのスコープに定義することもできる

package main

func main() {
    type Profile struct {
        Name string
        Age  int
    }

    p := &Profile{"Tanaka", 31}

    println(p.Name, p.Age)
}


type で型として定義する必要はないので以下のように使うこともできる。(テストでよく使う)

package main

func main() {
    // p := &struct{ のように & をつければポインタにできる
    p := struct {
        Name string
        Age  int
    }{
        Name: "Tanaka",
        Age:  31,
    }

    println(p.Name, p.Age)



    // スライスにもできる
    a := []struct {
        Name string
        Age  int
    }{
        {Name: "Tanaka", Age: 31},
        {Name: "Suzuki", Age: 46},
        {Name: "Matsui", Age: 45},
    }

    for i, v := range a {
        println(i, v.Name, v.Age)
    }



    // マップにもできる
    m := map[int]struct {
        Name string
        Age  int
    }{}

    m[0] = struct {
        Name string
        Age  int
    }{"Tanaka", 31}

    m[1] = struct {
        Name string
        Age  int
    }{"Suzuki", 46}

    m[2] = struct {
        Name string
        Age  int
    }{"Matsui", 45}

    for k, v := range m {
        println(k, v.Name, v.Age)
    }
}


type で定義しない場合でも型チェックはできる

package main

func main() {
    p := struct {
        Name string
        Age  int
    }{
        Name: "Tanaka",
        Age:  31,
    }

    // .(type) で判別
    switch (interface{}(p)).(type) {
    case struct {
        Name string
        Age  int
    }:
        println("is struct { Name string Age  int }")
    }

    // .(型) でも判別できる
    _, ok := (interface{}(p)).(struct {
        Name string
        Age  int
    })
    println("ok:", ok) // ok: true
}



構造体にメソッド(関数)を定義する

構造体にメソッドを持たせるには func (p Profile) ... のようにレシーバを使う。
定義した関数内では構造体のフィールドや他のメソッドを使うことができる。

package main

import "strconv"

type Profile struct {
    Name string
    Age  int
}

func (p *Profile) Set(name string, age int) {
    p.Name = name
    p.Age = age
}

func (p Profile) String() string {
    return "Name: " + p.Name + ", Age: " + strconv.Itoa(p.Age)
}

func (p Profile) Print() {
    s := p.String()
    println(s)
}

func main() {
    var p Profile
    p.Set("Tanaka", 31)
    p.Print() // Name: Tanaka, Age: 31
}

レシーバをポインタ/非ポインタのどちらを使うべきかは Receiver-Type がわかりやすい


関数(ここでは New)を定義して *Profile を返す。
他言語の「クラスを new してコンストラクタを呼び出す」のようなもの

package main

type Profile struct {
    Name string
    Age  int
}

func New(name string, age int) *Profile {
    return &Profile{
        Name: name,
        Age:  age,
    }
}

func (p Profile) Print() {
    println("name:", p.Name, ", age:", p.Age)
}

func main() {
    p := New("Tanaka", 31)
    p.Print()
}


標準の log パッケージの構造体 LoggerNew 関数で *Logger を返す。



メソッドチェーン

抽象的ですが、構造体を返すことでメソッドチェーンを作れます

package main

type A struct {}

func (a A) create_B() B {
    return B{}
}

type B struct {}

func (b B) create_C() C {
    return C{}
}

type C struct {}

func (c C) Hello() {
    println("hello")
}

func main() {
    var a A
    a.create_B().create_C().Hello()
}



構造体の埋め込み(継承)

他言語のクラスの継承はGo言語では埋め込み

package main

type A struct {
    Name string
    Age  int
}

type B struct {
    A
    // ポインタの場合は *A にして &A{} を渡す
}

func (b B) Print() {
    // 埋め込みで A の Name と Age が使える
    println("name:", b.Name, ", age:", b.Age)
    // 以下でも同じ
    println("name:", b.A.Name, ", age:", b.A.Age)
}

func main() {
    b := B{A{"Tanaka", 31}}
    b.Print() // name: Tanaka, age: 31
}


オーバーライド

構造体を埋め込んだ先に同名のフィールドやメソッドがあるときは上書きされる

package main

type A struct {
    Name string
    Age  int
}

func (a A) Print() {
    println("struct A", "name:", a.Name, "age:", a.Age)
}

type B struct {
    Name string // A の Name は上書きされる
    A
}

// A の Print 関数は上書きされる
func (b B) Print() {
    println("struct B", "name:", b.Name, "age:", b.Age)
    // 埋め込んだ A のフィールドや関数は b.A.Name とか b.A.Print() で使える
}

func main() {
    b := B{Name: "Suzuki", A: A{"Tanaka", 31}}

    b.Print()   // struct B name:  Suzuki age: 31

    // A の Print を使う
    b.A.Print() // struct A name: Tanaka age: 31
}



構造体のスライス(配列)とループ

package main

type Profile struct {
    Name string
    Age  int
}

func main() {
    // Profile は型名なので、[]string などと同じ
    p := []*Profile{
        {"Tanaka", 31},
        {"Suzuki", 46},
    }

    // 追加もスライスと同じ
    p = append(p, &Profile{"Matsui", 45})

    for _, v := range p {
        println(v.Name, v.Age)
    }
}



構造体の比較

reflect.DeepEqual はポインタか否かにかかわらず、フィールドの値が同じであれば true を返す

package main

import (
    "reflect"
)

type A struct {
    Value string
}

func main() {
    a1 := A{"value"}
    a2 := A{"value"}

    println(reflect.DeepEqual(a1, a2))   // true
    println(reflect.DeepEqual(&a1, &a2)) // true
    println(a1 == a2)                    // true
    println(&a1 == &a2)                  // false
}