更なる型:構造体、スライス、マップ

The Go Authors

目次

1. ポインタ

Goはポインタを扱います。 ポインタは値のメモリアドレスを指します。

変数 T のポインタは、 *T 型で、ゼロ値は nil です。

var p *int

& オペレータは、そのオペランド( operand )へのポインタを引き出します。

i := 42
p = &i

* オペレータは、ポインタの指す先の変数を示します。

fmt.Println(*p) // ポインタpを通してiから値を読みだす
*p = 21         // ポインタpを通してiへ値を代入する

これは "dereferencing" または "indirecting" としてよく知られています。

なお、C言語とは異なり、ポインタ演算はありません。

package main

import "fmt"

func main() {
    i, j := 42, 2701

    p := &i         // point to i
    fmt.Println(*p) // read i through the pointer
    *p = 21         // set i through the pointer
    fmt.Println(i)  // see the new value of i

    p = &j         // point to j
    *p = *p / 37   // divide j through the pointer
    fmt.Println(j) // see the new value of j
}

2. 構造体

struct (構造体)は、フィールド( field )の集まりです。

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    fmt.Println(Vertex{1, 2})
}

3. 構造体のフィールド

structのフィールドは、ドット( . )を用いてアクセスします。

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)
}

4. 構造体へのポインタ

structのフィールドは、structのポインタを通してアクセスすることもできます。

フィールド X を持つstructのポインタ p がある場合、フィールド X にアクセスするには (*p).X のように書くことができます。 しかし、この表記法は大変面倒ですので、Goでは代わりに p.X と書くこともできます。

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    p := &v
    p.X = 1e9
    fmt.Println(v)
}

5. 構造体リテラル

structリテラルは、フィールドの値を列挙することで新しいstructの初期値の割り当てを示しています。

Name: 構文を使って、フィールドの一部だけを列挙することができます(この方法でのフィールドの指定順序は関係ありません)。 例では X: 1 として X だけを初期化しています。

& を頭に付けると、新しく割り当てられたstructへのポインタを戻します。

package main

import "fmt"

type Vertex struct {
    X, Y int
}

var (
    v1 = Vertex{1, 2}  // has type Vertex
    v2 = Vertex{X: 1}  // Y:0 is implicit
    v3 = Vertex{}      // X:0 and Y:0
    p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
    fmt.Println(v1, p, v2, v3)
}

6. 配列

[n]T 型は、型 Tn 個の変数の配列( array )を表します。

以下は、intの10個の配列を宣言しています:

var a [10]int

配列の長さは、型の一部分です。ですので、配列のサイズを変えることはできません。 これは制約のように思えますが、心配しないでください。 Goは配列を扱うための便利な方法を提供しています。

package main

import "fmt"

func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)

    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)
}

7. スライス

配列は固定長です。一方で、スライスは可変長です。より柔軟な配列と見なすこともできます。 実際には、スライスは配列よりもより一般的です。

[]T は 型 T のスライスを表します。

コロンで区切られた二つのインデックス low と high の境界を指定することによってスライスが形成されます:

a[low : high]

これは最初の要素は含むが、最後の要素は除いた半開区間を選択します。

次の式は a の要素の内 1 から 3 を含むスライスを作ります。

a[1:4]
package main

import "fmt"

func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13}

    var s []int = primes[1:4]
    fmt.Println(s)
}

8. スライスは配列への参照のようなもの

スライスは配列への参照のようなものです。

スライスはどんなデータも格納しておらず、単に元の配列の部分列を指し示しています。

スライスの要素を変更すると、その元となる配列の対応する要素が変更されます。

同じ元となる配列を共有している他のスライスは、それらの変更が反映されます。

package main

import "fmt"

func main() {
    names := [4]string{
        "John",
        "Paul",
        "George",
        "Ringo",
    }
    fmt.Println(names)

    a := names[0:2]
    b := names[1:3]
    fmt.Println(a, b)

    b[0] = "XXX"
    fmt.Println(a, b)
    fmt.Println(names)
}

9. スライスリテラル

スライスのリテラルは長さのない配列リテラルのようなものです。

これは配列リテラルです:

[3]bool{true, true, false}

そして、これは上記と同様の配列を作成し、それを参照するスライスを作成します:

[]bool{true, true, false}
package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}

10. スライスの既定値

スライスするときは、それらの既定値を代わりに使用することで上限または下限を省略することができます。

既定値は下限が 0 で上限はスライスの長さです。

以下の配列において

var a [10]int

これらのスライス式は等価です:

a[0:10]
a[:10]
a[0:]
a[:]
package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}

    s = s[1:4]
    fmt.Println(s)

    s = s[:2]
    fmt.Println(s)

    s = s[1:]
    fmt.Println(s)
}

11. スライスの長さと容量

スライスは長さ( length )と容量( capacity )の両方を持っています。

スライスの長さは、それに含まれる要素の数です。

スライスの容量は、スライスの最初の要素から数えて、元となる配列の要素数です。

スライス s の長さと容量は len(s)cap(s) という式を使用して得ることができます。

十分な容量を持って提供されているスライスを再スライスすることによって、スライスの長さを伸ばすことができます。 その容量を超えて伸ばしたときに何が起こるかを見るため、プログラム例でのスライスのいずれかの操作を変更してみてください。

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // Slice the slice to give it zero length.
    s = s[:0]
    printSlice(s)

    // Extend its length.
    s = s[:4]
    printSlice(s)

    // Drop its first two values.
    s = s[2:]
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

12. nilスライス

スライスのゼロ値は nil です。

nil スライスは 0 の長さと容量を持っており、何の元となる配列も持っていません。

package main

import "fmt"

func main() {
    var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {
        fmt.Println("nil!")
    }
}

13. make関数によるスライスの作成

スライスは、組み込みの make 関数を使用して作成することができます。 これは、動的サイズの配列を作成する方法です。

make 関数はゼロ化された配列を割り当て、その配列を指すスライスを返します。

a := make([]int, 5)  // len(a)=5

make の3番目の引数に、スライスの容量( capacity )を指定できます。 cap(b) で、スライスの容量を返します:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
package main

import "fmt"

func main() {
    a := make([]int, 5)
    printSlice("a", a)

    b := make([]int, 0, 5)
    printSlice("b", b)

    c := b[:2]
    printSlice("c", c)

    d := c[2:5]
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

14. スライスのスライス

スライスは、他のスライスを含む任意の型を含むことができます。

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Create a tic-tac-toe board.
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // The players take turns.
    board[0][0] = "X"
    board[2][2] = "O"
    board[1][2] = "X"
    board[1][0] = "O"
    board[0][2] = "X"

    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }
}

15. スライスへの要素の追加

スライスへ新しい要素を追加するには、Goの組み込みの append を使います。 append についての詳細は documentation を参照してみてください。

func append(s []T, vs ...T) []T

上の定義を見てみましょう。 append への最初のパラメータ s は、追加元となる T 型のスライスです。 残りの vs は、追加する T 型の変数群です。

append の戻り値は、追加元のスライスと追加する変数群を合わせたスライスとなります。

もし、元の配列 s が、変数群を追加する際に容量が小さい場合は、より大きいサイズの配列を割り当て直します。 その場合、戻り値となるスライスは、新しい割当先を示すようになります。

(スライスについてより詳しく学ぶには、Slices: usage and internalsを読んでみてください)

package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

    // append works on nil slices.
    s = append(s, 0)
    printSlice(s)

    // The slice grows as needed.
    s = append(s, 1)
    printSlice(s)

    // We can add more than one element at a time.
    s = append(s, 2, 3, 4)
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

16. range

for ループに利用する range は、スライスや、マップ( map )をひとつずつ反復処理するために使います。

スライスをrangeで繰り返す場合、rangeは反復毎に2つの変数を返します。 1つ目の変数はインデックス( index )で、2つ目はインデックスの場所の要素のコピーです。

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}

17. range(続)

インデックスや値は、 " _ "(アンダーバー) へ代入することで捨てることができます。

もしインデックスだけが必要なのであれば、 " , value " を省略します。

package main

import "fmt"

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

18. 練習:スライス

Pic 関数を実装してみましょう。 このプログラムを実行すると、生成した画像が下に表示されるはずです。 この関数は、長さ dy のsliceに、各要素が8bitのunsigned int型で長さ dx のsliceを割り当てたものを返すように実装する必要があります。 画像は、整数値をグレースケール(実際はブルースケール)として解釈したものです。

生成する画像は、好きに選んでください。例えば、面白い関数に、 (x+y)/2x*yx^y などがあります。

ヒント:( [][]uint8 に、各 []uint8 を割り当てるためにループを使用する必要があります)

ヒント:( uint8(intValue) を型の変換のために使います)

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
}

func main() {
    pic.Show(Pic)
}

19. マップ

map はキーと値とを関連付けます(マップします)。

マップのゼロ値は nil です。 nil マップはキーを持っておらず、またキーを追加することもできません。

make 関数は指定された型の、初期化され使用できるようにしたマップを返します。

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
}

20. マップリテラル

mapリテラルは、structリテラルに似ていますが、 キー ( key )が必要です。

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

func main() {
    fmt.Println(m)
}

21. マップリテラル(続)

もし、mapに渡すトップレベルの型が単純な型名である場合は、リテラルの要素から推定できますので、その型名を省略することができます。

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

func main() {
    fmt.Println(m)
}

22. マップの操作

map m の操作を見ていきましょう。

m へ要素(elem)の挿入や更新:

m[key] = elem

要素の取得:

elem = m[key]

要素の削除:

delete(m, key)

キーに対する要素が存在するかどうかは、2つの目の値で確認します:

elem, ok = m[key]

もし、 mkey があれば、変数 oktrue となり、存在しなければ、 okfalse となります。

なお、mapに key が存在しない場合、 elem はmapの要素の型のゼロ値となります。

Note: もし elemok を宣言していないのであれば、次のように := で短く表現できます:

elem, ok := m[key]
package main

import "fmt"

func main() {
    m := make(map[string]int)

    m["Answer"] = 42
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 48
    fmt.Println("The value:", m["Answer"])

    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
}

23. 練習:マップ

WordCount 関数を実装してみましょう。string s で渡される文章の、各単語の出現回数のmapを返す必要があります。 wc.Test 関数は、引数に渡した関数に対しテストスイートを実行し、成功か失敗かを結果に表示します。

strings.Fields で、何かヒントを得ることができるはずです。

Note: このテストスイートで何を入力とし、何を期待しているかについては、golang.org/x/tour/wcを見てみてください。

package main

import (
    "golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
    return map[string]int{"x": 1}
}

func main() {
    wc.Test(WordCount)
}

24. 関数値

関数も変数です。他の変数のように関数を渡すことができます。

関数値( function value )は、関数の引数に取ることもできますし、戻り値としても利用できます。

package main

import (
    "fmt"
    "math"
)

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}

25. クロージャ(関数閉包)

Goの関数は クロージャ( closure ) です。 クロージャは、それ自身の外部から変数を参照する関数値です。 この関数は、参照された変数へアクセスして変えることができ、その意味では、その関数は変数へ"バインド"( bind )されています。

例えば、 adder 関数はクロージャを返しています。 各クロージャは、それ自身の sum 変数へバインドされます。

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

26. 練習:フィボナッチクロージャ

関数を用いた面白い例を見てみましょう。

fibonacci (フィボナッチ)関数を実装しましょう。この関数は、連続するフィボナッチ数(0, 1, 1, 2, 3, 5, ...)を返す関数(クロージャ)を返します。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

27. Congratulations!

この章はこれで終わりです。

章のリストから学びたいところを見ても良いですし、 > をクリックして次の章へ進みましょう。