Go言語(golang) 構造体の定義と使い方
構造体(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 パッケージの構造体 Logger
は New
関数で *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 }