引き続き 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 だ!とりあえず一区切り。