構造体

構造体は、既存の型を組み合わせて新しい型を作る機能だ。とりあえずは C の構造体を同じようなものだと考えればいい。
構造体の定義は type キーワードを使って次のようにする。

type 型名 struct {
    フィールド1 型1
    フィールド2 型2
    ...
}

構造体を構成するデータは、フィールドと呼ばれる。上のようにフィールドとその型を列記すればいい。

構造体の初期化は、構造体名{フィールド1, フィールド2, …} のようにフィールドを順に並べるほか、フィールド名とそのデータを組みにして渡す方法がある。後者では定義と順番が異なってもいい。また、フィールドへのアクセスは . 演算子を使う。例を示そう。

package main

import (
    "fmt"
    "math"
)

type Point struct {
    x float64
    y float64
}

func distance(p, q Point) float64 {
    dx := p.x - q.x
    dy := p.y - q.y
    return math.Sqrt(dx * dx + dy * dy)
}

func main() {
    var o Point
    var p Point = Point{ 10.0, 10.0 }
    var q Point = Point{ y: 200.0, x: 100.0 }

    fmt.Println(o)
    fmt.Println(p)
    fmt.Println(q)

    fmt.Println(o.x)
    fmt.Println(o.y)
    fmt.Println(p.x)
    fmt.Println(p.y)
    fmt.Println(q.x)
    fmt.Println(q.y)

    fmt.Println(distance(o, p))
    fmt.Println(distance(o, q))
    fmt.Println(distance(p, q))
}

ここでは Point という名前の構造体を使っている。構造体の初期化は、main 関数の冒頭で行っている。変数 pq は上に書いたように初期化している。変数 o は初期化していないので、自動的にゼロ値に初期化される。
distance 関数の中で、構造体のフィールドにアクセスしている。

^o^ > go run struct.go
{0 0}
{10 10}
{100 200}
0
0
10
10
100
200
14.142135623730951
223.60679774997897
210.23796041628637

次は、構造体のポインタの例を示そう。構造体の場合も普通の型のように、アドレスを取得するには & を、値にアクセスするには * を使えばいい。ただし、フィールドにアクセスするときは、C のように -> を使うのではなく、. を使う。このへんは構造体の変数なのかポインタなのかを気にしなくていいので楽だな。

package main

import (
    "fmt"
    "math"
)

type Point struct {
    x float64
    y float64
}

func distance(p, q *Point) float64 {
    dx := p.x - q.x
    dy := p.y - q.y
    return math.Sqrt(dx * dx + dy * dy)
}

func main() {
    var o *Point = &Point{}
    var p *Point = &Point{ 10.0, 10.0 }
    var q *Point = new(Point)
    q.x, q.y = 100.0, 200.0

    fmt.Println(o)
    fmt.Println(p)
    fmt.Println(q)

    fmt.Println(o.x)
    fmt.Println(o.y)
    fmt.Println(p.x)
    fmt.Println(p.y)
    fmt.Println(q.x)
    fmt.Println(q.y)

    fmt.Println(distance(o, p))
    fmt.Println(distance(o, q))
    fmt.Println(distance(p, q))
}
^o^ > go run struct2.go
&{0 0}
&{10 10}
&{100 200}
0
0
10
10
100
200
14.142135623730951
223.60679774997897
210.23796041628637

構造体をポインタとして使うときは、初期化用の関数を使うのが通例のようだ。次の例では、newPoint 関数がそれにあたる。この関数は、ポインタのフィールドになる値を引数にとって、構造体のポインタを返す。

package main

import (
    "fmt"
    "math"
)

type Point struct {
    x float64
    y float64
}

func newPoint(x, y float64) *Point {
    p := new(Point)
    p.x, p.y = x, y
    return p
}

func distance(p, q *Point) float64 {
    dx := p.x - q.x
    dy := p.y - q.y
    return math.Sqrt(dx * dx + dy * dy)
}

func main() {
    var p *Point = newPoint(0.0, 0.0)
    var q *Point = newPoint(10.0, 10.0)

    fmt.Println(p)
    fmt.Println(q)

    fmt.Println(p.x)
    fmt.Println(p.y)
    fmt.Println(q.x)
    fmt.Println(q.y)

    fmt.Println(distance(p, q))
}
^o^ > go run struct3.go
&{0 0}
&{10 10}
0
0
10
10
14.142135623730951

最後に、構造体をスライスに格納する例を示して今日は終わりにしよう。構造体だからと言って何も特別なことはない。

package main

import "fmt"

type Point struct {
    x, y float64
}

func newPoint(x, y float64) *Point {
    p := new(Point)
    p.x, p.y = x, y
    return p
}

func main() {
    var a []Point = []Point{
        { x: 0.0, y: 0.0 },
        { 10.0, 10.0 },
        { 100.0, 100.0 },
    }
    var b []*Point = make([]*Point, 8)

    fmt.Println(a)
    fmt.Println(b)

    for i := 0; i < 8; i++ {
        b[i] = newPoint(float64(i), float64(i))
    }
    fmt.Println(b)
    for i := 0; i < 8; i++ {
        fmt.Println(b[i])
    }
}
^o^ > go run struct_slice.go
[{0 0} {10 10} {100 100}]
[       ]
[0xc042008270 0xc042008280 0xc042008290 0xc0420082a0 0xc0420082b0 0xc0420082c0 0xc0420082d0 0xc0420082e0]
&{0 0}
&{1 1}
&{2 2}
&{3 3}
&{4 4}
&{5 5}
&{6 6}
&{7 7}