かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Go 言語勉強ログ その 2

blog.okazuki.jp

引き続き A Tour of Go していきます。

ポインター

Go 言語にはポインターがある。見初期化状態でも nil になる。 C 言語と同じで & でポインターを取得できて * でポインターの先の実態を見ることが出来る。

package main

import (
    "fmt"
)

func main() {
    i := 0
    var p *int = &i // p := &i でいいけどポインター型の変数の宣言の確認もかねて
    *p = 10
    fmt.Printf("i = %v, *p = %v\n", i, *p) // i = 10, *p = 10
}

Java 言語がポインターが無い!といいつつ実態はポインターみたいな参照があるよ!っていうのはポインターの無い言語というスタンスのためだったのかなぁとか勝手に思ってるけど Go は素直にポインターと言ってて、いいと思う。 Java と同じで危険なポインター演算は出来ない。安全そう。

最近流行りの null の無い言語ではないみたいです。

構造体

複数の変数を意味ある名前をつけてまとめ上げるものかな。書き方は以下のように type 構造体名 struct { ... } のような感じ。

package main

import (
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{"Tanaka", 39}
    fmt.Println(p)
    p.Age++
    fmt.Println(p)
}

実行するとこんな感じ。

{Tanaka 39}
{Tanaka 40}

構造体もポインター可能。C/C++ だとポインターの場合はアロー演算子(->)でメンバーにアクセスしてたけど Go では普通に . でいい。

package main

import (
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{"Tanaka", 39}
    ref := &p
    (*ref).Age = 100 // 冗長
    fmt.Println(p)
    ref.Age = 110 // . でアクセスできる
    fmt.Println(p)
}

実行するとこんな感じ。

{Tanaka 100}
{Tanaka 110}

色々な構造体の初期化方法がある。全フィールドを初期化する方法、まったく初期化しない方法、特定のものだけ初期化する方法、ポインターを返すものとか。

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    a := Person{"Tanaka", 39}   // 全指定
    b := Person{}               // 指定なし
    c := Person{Name: "Kimura"} // 名前指定
    d := &Person{"Sakata", 100} // ポインタを返す

    fmt.Printf("a = %v, b = %v, c = %v, d = %v\n", a, b, c, d)
}

配列

次は配列。配列は他の言語と違って型名の前に[]を書いて配列の型を表すみたい。

package main

import "fmt"

func main() {
    var array [2]string
    array[0] = "a"
    array[1] = "b"

    fmt.Println(array[0], array[1]) // a b
}

慣れないなぁ。

宣言と同時に初期化するときは []型名{ 配列の要素カンマ区切り } みたいになる。

package main

import "fmt"

func main() {
    array := [2]string{"a", "b"}
    fmt.Println(array[0], array[1])
}

スライスというものを使って配列の一部を切り取ってみるみたいなことが出来る。コピーじゃなくて参照っぽいのでスライスした先を書き換えると元も書き換わる。

package main

import (
    "fmt"
)

func main() {
    array := [4]string{"a", "b", "c", "d"}
    slice1 := array[1:2] // b
    slice2 := array[:2]  // a, b
    slice3 := array[1:]  // b, c, d

    for i, s := range [][]string{slice1, slice2, slice3} {
        fmt.Printf("%v: len=%v, cap=%v, %v\n", i, len(s), cap(s), s)
    }

    slice1[0] = "B"

    for i, s := range [][]string{slice1, slice2, slice3} {
        fmt.Printf("%v: len=%v, cap=%v, %v\n", i, len(s), cap(s), s)
    }
}

実行結果

0: len=1, cap=3, [b]
1: len=2, cap=4, [a b]
2: len=3, cap=3, [b c d]
0: len=1, cap=3, [B]
1: len=2, cap=4, [a B]
2: len=3, cap=3, [B c d]

range を使うことで foreach みたいなことが出来るというのを見たので使ってみた。

個人的に直感的じゃないのはスライス作るときに下限、上限を指定するところで下限の要素は含まれるけど上限のインデックスは含まれないところ。

配列からじゃなくても直接スライスを作ることも出来る。

package main

import "fmt"

func main() {
    slice1 := []int{1, 2, 3}
    fmt.Printf("len=%v, cap=%v, %v\n", len(slice1), cap(slice1), slice1)
}

実行結果

len=3, cap=3, [1 2 3]

今まで len と cap をしれっと使ってるけど、これは長さとキャパシティになる。cap は元の配列とかの長さに依存するのかな?キャパシティがあれば再スライスで伸ばすことが出来るみたい。

package main

import (
    "fmt"
)

func main() {
    array := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    s := array[:2]
    fmt.Printf("len=%v, cap=%v, %v\n", len(s), cap(s), s)
    s = s[3:8] // 再スライス
    fmt.Printf("len=%v, cap=%v, %v\n", len(s), cap(s), s)
}
len=2, cap=10, [0 1]
len=5, cap=7, [3 4 5 6 7]

make 関数というのを使って動的にスライスを作ることが出来る。第一引数に型、第二引数に長さ、第三引数は省略可能でキャパシティになる。

こんな感じ。

package main

import (
    "fmt"
)

func main() {
    // type, len
    s := make([]int, 10)
    fmt.Printf("len=%v, cap=%v, %v\n", len(s), cap(s), s)
    // type, len, cap
    s = make([]int, 0, 10)
    fmt.Printf("len=%v, cap=%v, %v\n", len(s), cap(s), s)

    // 試しに範囲外まで伸ばしてみる
    s = s[:100]
    fmt.Printf("len=%v, cap=%v, %v\n", len(s), cap(s), s)
}

試しに範囲外まで伸ばしてみたけど、エラーで落ちました。

len=10, cap=10, [0 0 0 0 0 0 0 0 0 0]
len=0, cap=10, []
panic: runtime error: slice bounds out of range

スライスを入れ子にして二次元配列みたいなことも出来る。

package main

import (
    "fmt"
)

func main() {
    // 2 次元配列的な
    s := [][]int{
        []int{1, 2, 3},
        []int{4, 5, 6},
        []int{7, 8, 9},
    }
    fmt.Printf("%v\n", s)

    // アクセスするときはこんな感じ
    s[1][1] = 100
    fmt.Printf("%v\n", s)

    // 当然スライス出来る
    s = s[:2]
    s[0] = s[0][1:]
    fmt.Printf("%v\n", s)
}

実行結果

[[1 2 3] [4 5 6] [7 8 9]]
[[1 2 3] [4 100 6] [7 8 9]]
[[2 3] [4 100 6]]

スライスのコピーとかはこんな感じになる

package main

import "fmt"

func main() {
    org := []int{1, 2, 3, 4, 5}
    dist := make([]int, len(org))
    for i, v := range org {
        dist[i] = v
    }

    fmt.Printf("%v, %v\n", org, dist)
}

組み込み関数の copy を使うのが安パイ

package main

import "fmt"

func main() {
    org := []int{1, 2, 3, 4, 5}
    dist := make([]int, len(org))
    copy(dist, org)

    fmt.Printf("%v, %v\n", org, dist)
}

要素の追加もこんなんでいける

package main

import "fmt"

func myAppend(a []int, values ...int) []int {
    r := a
    if cap(a) < len(a)+len(values) {
        r = make([]int, len(a)+len(values))
        copy(r, a)
    }

    copy(r[len(a):], values)
    return r
}

func main() {
    org := []int{1, 2, 3, 4, 5}
    org = myAppend(org, 10, 20, 30)
    fmt.Printf("%v\n", org)
}

myAppend という名前からもわかるように、組み込みの append 関数を使うのが安パイ

package main

import "fmt"

func main() {
    org := []int{1, 2, 3, 4, 5}
    org = append(org, 10, 20, 30)
    fmt.Printf("%v\n", org)
}

可変長引数にスライスとかを渡したい場合は、後ろに ... を付ける

package main

import "fmt"

func main() {
    org := []int{1, 2, 3, 4, 5}
    values := []int{10, 20, 30}
    org = append(org, values...)
    fmt.Printf("%v\n", org)
}

ループするときの range は インデックスと値のコピーが来るけど、使わない場合は _ で受けることで捨てることが出来る。

package main

import "fmt"

func main() {
    a := []int{1, 2, 3, 4, 5}
    // インデックスは使わないので捨てる
    for _, v := range a {
        fmt.Printf("%v\n", v)
    }
}

map

連想配列とかハッシュテーブルって呼ばれる類のものですね。

package main

import "fmt"

func main() {
    var m = make(map[string]string)
    m["a"] = "A"
    m["b"] = "B"
    m["c"] = "C"

    fmt.Printf("%v, %v, %v, %v\n", m["a"], m["b"], m["c"], m["d"])
}

実行結果

A, B, C,

存在しないキーにアクセスしてもエラーにはならずに nil っぽいです。

同じ内容をマップリテラルで書くとこんな感じ。

package main

import "fmt"

func main() {
    var m = map[string]string{
        "a": "A",
        "b": "B",
        "c": "C",
    }

    fmt.Printf("%v, %v, %v, %v\n", m["a"], m["b"], m["c"], m["d"])
}

構造体を値に指定することももちろん OK

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    // もちろん構造体も OK
    var m = map[string]Person{
        "a": Person{"Tanaka", 39}, // 明示的に構造体名指定したり
        "b": {"Kimura", 20}, // 明らかなので省略したりできる
        "c": {"piecent", 100},
    }

    fmt.Printf("%v, %v, %v, %v\n", m["a"], m["b"], m["c"], m["d"])
}

要素の削除は delete 関数で行う。あと、[] でアクセスしたときの2つ目の戻り値が要素の有無を返してくれるので、それで存在チェックが出来る。

package main

import "fmt"

func dump(m map[string]string, key string) {
    // 存在チェック
    if v, ok := m[key]; ok {
        fmt.Printf("%v あったよ! %v\n", key, v)
    } else {
        fmt.Printf("%v なかったよ\n", key)
    }
}

func main() {
    m := map[string]string{
        "a": "A",
        "b": "B",
        "c": "C",
    }

    delete(m, "b") // b を削除

    // 表示してみる
    for _, v := range []string{"a", "b", "c"} {
        dump(m, v)
    }
}

関数

関数を変数に突っ込んだりできる。高階関数とかって言われるやつが出来ますね。

package main

import "fmt"

// 関数を渡して関数を返す関数
func logable(f func(int) int) func(int) int {
    count := 0
    return func(i int) int {
        count++
        r := f(i)
        fmt.Printf("%v times called, arg is %v, return value is %v\n", count, i, r)
        return r
    }
}

func main() {
    // 関数を変数に代入
    f1 := func(i int) int {
        return i * i
    }

    // 呼べる
    fmt.Printf("%v\n", f1(10))

    f2 := logable(f1) // 関数を渡して関数を返す関数を呼ぶ
    // もちろん f2 も呼べる
    fmt.Printf("%v\n", f2(10))
    fmt.Printf("%v\n", f2(100))
    fmt.Printf("%v\n", f2(20))
}

実行結果

100
1 times called, arg is 10, return value is 100
100
2 times called, arg is 100, return value is 10000
10000
3 times called, arg is 20, return value is 400
400

次は Methods and interfaces だ!とりあえず一区切り。