Help us understand the problem. What is going on with this article?

プログラミング言語Goを読みながらメモ(第四章)

More than 1 year has passed since last update.

プログラミング言語 Go を読みながらメモ。

第一章 : https://qiita.com/Nabetani/items/077c6b4d3d1ce0a2c3fd
第二章 : https://qiita.com/Nabetani/items/d053304698dfa3601116
第三章 : https://qiita.com/Nabetani/items/2fd9c372fcd8383955a5

で。

配列

配列の要素が比較可能であればその配列型も比較可能

とあるので、不等号も試してみた。

go
package main

import (
    "fmt"
)

func main() {
    a := [2]int{1, 2}
    b := [2]int{3, 4}
    fmt.Println(a < b) // invalid operation: a < b (operator < not defined on array)
}

駄目なのか。

スライス

配列と異なり、スライスは比較可能ではありません

そうなのか。何のために比較不能にしたんだろうか...と思ったら、98ページに書いてあった。
何故そうなのかが書いてるのは素晴らしいことだと思う。

とまれ。エラーを出してみる:

go
package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2}
    b := []int{3, 4}
    fmt.Println(a == b) //  invalid operation: a == b (slice can only be compared to nil)
}

なるほど。

しかし、97ページの equal のような関数を見ると、C++ のようなテンプレートが欲しくなるね。我慢我慢。

スライスの append

go
package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 3}
    b := a[:1]
    fmt.Printf("a=%v, b=%v, cap(b)=%v\n", a, b, cap(b))
    for i := 1; i <= 4; i++ {
        b = append(b, i*100)
        b[0] = i * 111
        fmt.Printf("a=%v, b=%v, cap(b)=%v\n", a, b, cap(b))
    }
}

とやると、

a=[1 2 3], b=[1], cap(b)=3
a=[111 100 3], b=[111 100], cap(b)=3
a=[222 100 200], b=[222 100 200], cap(b)=3
a=[222 100 200], b=[333 100 200 300], cap(b)=6
a=[222 100 200], b=[444 100 200 300 400], cap(b)=6

と出力される。
まあわかっていればそういうもんかとも思うけど、わかりにくいバグの原因になりがちな気もする。気をつけよう。

あと。範囲外へのアクセスも面白い。

go
package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7}
    b := a[:1]
    fmt.Printf("len(b)=%v  b[4:5]=%v\n", len(b), b[4:5]) 
    //=> len(b)=1  b[4:5]=[5]
    fmt.Println(b[4]) // panic: runtime error: index out of range
}

スライスを取るのは合法だけど、値を取りに行くのは違法。なんでだろう。

マップ

マップの要素のアドレスは取れないらしい。では、スライスはどうか。

go
package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7}
    fmt.Printf("&a[1]=%p\n", &a[1])
}

取れる。一安心。

map 型の要素アクセスの二番目の返戻値にその要素が存在するかどうかが入っているというのは面白いアイディアだと思った。

go
package main

import (
    "fmt"
)

func main() {
    a := map[string]int{"alice": 1, "bob": 2}
    fmt.Printf("a=%v\n", a)
    bv, bok := a["bob"]
    fmt.Printf(`a["bob"] = %v, %v`+"\n", bv, bok) //=>a["bob"] = 2, true
    cv, cok := a["charlie"]
    fmt.Printf(`a["charlie"] = %v, %v`+"\n", cv, cok) //=>a["charlie"] = 0, false
}

nil map でも、要素へのアクセスが出来るのは不思議な気もする。以下の通り:

go
package main

import (
    "fmt"
)

func main() {
    var a map[string]int
    fmt.Printf("a=%[1]v %[1]T\n", a) //=> a=map[] map[string]int
    bv, bok := a["bob"]
    fmt.Printf(`a["bob"] = %v, %v`+"\n", bv, bok) //=>a["bob"] = 0, false
}

一方。
スライスは、nil だと 長さは取れるものの要素にアクセスを試みると panic になる。以下の通り:

go
package main

import (
    "fmt"
)

func main() {
    var slice []int
    fmt.Println(len(slice)) //=> 0
    fmt.Println(slice[0])   //=> panic: runtime error: index out of range
}

構造体

構造体のポインタからメンバを参照する(C言語で言う -> 演算子を使う場面)で、単なるピリオドが使えるらしい。便利なような、紛らわしいような。

じゃあポインタのポインタならどうなる?

go
package main

import "fmt"

type someType struct {
    ID int
}

func main() {
    t := someType{123}
    p := &t
    q := &p
    fmt.Println(q)
    fmt.Println(t.ID) // もちろん okay
    fmt.Println(p.ID) // okay
    fmt.Println(q.ID) // q.ID undefined (type **SomeType has no field or method ID)
}

駄目みたい。まあそうか。

pointerToPoint := &Point{1,2} が面白い。C言語脳だとちょっと混乱する。

無名フィールド

これはクラスの継承っぽい。しかも多重継承。
継承っぽいけど、無名のメンバーがあるだけなので is_a? の関係はなくていい。面白い。

冗長な記述を許すことで同名のフィールドが合っても大丈夫なようにしているところも C++ の多重継承っぽい動き。

JSON

Marshal

json.MarshalIndent(略) の、第一返戻値が string ではなく []byte なのは何故だろう。
そういえば C# もそうだったような気が。

Unmarshal

go
package main

import (
    "encoding/json"
    "fmt"
)

type Point struct {
    X int
    Y int
}

type Circle struct {
    Point
    Radius int
}

type Wheel struct {
    Circle
    Spoke int
}

func main() {
    js := `{
           "X": 6,
           "Y": 7,
           "Spoke": 0,
           "NoSuchField": 1234
    }`
    var w Wheel
    var data []byte = []byte(js)
    json.Unmarshal(data, &w)
    fmt.Printf("%#v\n", w)
    //=> main.Wheel{Circle:main.Circle{Point:main.Point{X:6, Y:7}, Radius:0}, Spoke:0}
}

構造体のメンバにあって JSON にない場合はゼロ初期化。
構造体のメンバになくて JSON にある場合は無視。
わかりやすくて好ましい。

go
type Point struct {
    X int `json:"xx"`
    Y int `json:"yy"`
}

という文法は正直意外。
区切り文字なしで注釈を文字列として書くのか。

テキストテンプレート

不思議な機能が。ruby の ERB みたいなものか。
この本のテンプレートのソースコードは難しく、理解できなかった。
仕方ないので https://golang.org/pkg/text/template/ を見た。

上記サイトに有るソースをそのまま(フォーマットだけ整えて)書いておくと:

go
package main

import (
    "html/template"
    "os"
)

func main() {
    type Inventory struct {
        Material string
        Count    uint
    }
    sweaters := Inventory{"wool", 17}
    tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
    if err != nil {
        panic(err)
    }
    err = tmpl.Execute(os.Stdout, sweaters)
    if err != nil {
        panic(err)
    }
}

これならわかる。

template 内の range を使った例は、こんな感じ:

go
package main

import (
    "os"
    "text/template"
)

func main() {
    type Inventory struct {
        Material string
        Count    uint
    }
    sweaters := []Inventory{Inventory{"<wool>", 17}, Inventory{"<cotton>", 20}}
    tmpl, err := template.New("test").Parse(`Sweaters : 
{{range .}}{{.Count}} items are made of {{.Material}}
{{end}}`)
    if err != nil {
        panic(err)
    }
    err = tmpl.Execute(os.Stdout, sweaters)
    if err != nil {
        panic(err)
    }
}

range this とか range self とか書きたくなる場所には range . と書くらしい。

出力は

Sweaters : 
17 items are made of <wool>
20 items are made of <cotton>

template 用の言語が Go そのものとは別の文法になっているところがダサいし覚えるのが大変だけど、その分安全なんだと思う。まあ安全のほうが大事だよね。

HTML テンプレート

エスケープの仕方が違うだけで、普通の template とほぼ同じみたい。

go
package main

import (
    "html/template"
    "os"
)

func main() {
    type Inventory struct {
        Material string
        Count    uint
        Hoge     template.HTML // エスケープされない
    }
    sweaters := []Inventory{
        Inventory{"<wool>", 17, "<hr/>"},
        Inventory{"<cotton>", 20, "<hr noshade/>"}, // ここにコンマが必要なのは何故だろう
    }
    tmpl, err := template.New("test").Parse(
        `<h1>Sweaters</h1>
<table><tr><th>material</th><th>count</th><th>hoge</th></tr>
{{range .}}<tr>
    <td> {{.Material}}</td>
    <td>{{.Count}}</td>
    <td>{{.Hoge}}</td>
</tr>
{{end}}</table>
`)
    if err != nil {
        panic(err)
    }
    err = tmpl.Execute(os.Stdout, sweaters)
    if err != nil {
        panic(err)
    }
}

出力は、こんな感じ:

<h1>Sweaters</h1>
<table><tr><th>material</th><th>count</th><th>hoge</th></tr>
<tr>
    <td> &lt;wool&gt;</td>
    <td>17</td>
    <td><hr/></td>
</tr>
<tr>
    <td> &lt;cotton&gt;</td>
    <td>20</td>
    <td><hr noshade/></td>
</tr>
</table>

第四章はこれで終り。
続いて楽しそうな第五章。
https://qiita.com/Nabetani/items/4b785f1c9b0b26d48475

Nabetani
横浜へなちょこプログラミング勉強会をやっていました。 / CodeIQ の出題者でした。 / 日経 WinPC に連載を持っていました(名義が違うけど) / Yokohama rb に半分ぐらい参加しています。 / twitter : http://twitter.com/Nabetani
https://nabetani.hatenadiary.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした