定型データの入力

定型のデータを読み込んで、型変換をしてくれる関数があると便利だ。
fmt パッケージの ScanFscan は空白文字で区切られたテキストデータを読み込み、可変長引数で渡されたデータ形式の変数(のポインタ)に変換して格納してくれる。Scan は標準入力から、Fscanio.Reader から読み込む。
まずは Scan の例。

^o^ > go run scan.go
1 1.5 1
2 2.3 2
3 3.8 3
6 7.6 [1 2 3]

go run scan.go コマンドに続く3行が入力で、1行ごとに整数、実数、文字列に変換される。ctrl + c で入力を終了すると、結果が表示される。最後の行が結果だ。

次は Fscan の例。io.Reader から(つまりファイルから)読み込む。

^o^ > cat fscan_sample.txt
10 1.234 foo
20 5.678 bar
30 9.876 baz

^o^ > go run fscan.go fscan_sample.txt
60 16.788 [foo bar baz]

ScanfFscanf は書式付き入力だ。引数で指定された書式で入力を解釈して読み込んでくれる。Scanf は標準入力から、Fscanfio.Reader (つまりファイル)から読み込む。
Scanf の例。

この例では整数、実数、クォートされた文字列をカンマ区切りしたものを入力として期待している。

^o^ > go run scanf.go
10,1.234,"foo"
20,5.678,"bar"
30,9.876,"baz"
60 16.788 [foo bar baz]

最後に Fscanf。これはファイルから読み込むことのほかは Scanf と同じだ。

^o^ > cat fscanf_sample.txt
10,1.234,"foo"
20,5.678,"bar"
30,9.876,"baz"

^o^ > go run fscanf.go fscanf_sample.txt
60 16.788 [foo bar baz]

cat

ファイル入出力の練習に cat コマンドを写経してみた。
まずひとつめ、bufioReadString 関数で1行ずつ処理。

^o^ > go build cat.go

^o^ > .\cat cat.go cat2.go
package main

import (
        "os"
        "fmt"
        "io"
        "bufio"
)

func cat(filename string) {
        file, err := os.Open(filename)
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
        }
        rd := bufio.NewReader(file)
        for {
                s, err := rd.ReadString('\n')
                if err == io.EOF { break }
                fmt.Print(s)
        }
        file.Close()
}

func main() {
        for _, name := range os.Args[1:] {
                cat(name)
        }
}
package main

import (
        "os"
        "fmt"
        "io/ioutil"
)

func cat(filename string) {
        buff, err := ioutil.ReadFile(filename)
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
        }
        os.Stdout.Write(buff)
}

func main() {
        for _, name := range os.Args[1:] {
                cat(name)
        }
}

もうひとつ、io/ioutil パッケージの ReadFile 関数でファイルまるごと読み込む。

実行結果はひとつめと同じなので省略。

ファイル入出力

今日はファイル入出力。
前に書いたように、ファイル入出力にはファイルディスクリプタを使う。ファイルディスクリプタは、os パッケージの Open 関数で取得する。取得したファイルディスクリプタで、ファイルからの入力には Read 関数、出力には Write 関数を使い、終わったら Close する。まあ、普通の手順だよな。
以下、サンプル。testin.txt ファイルから読み込んだ内容を testout.txt ファイルに書き込んでいる。

^o^ > cat testin.txt
Hello, Golang!

^o^ > go run fileio.go

^o^ > cat testout.txt
Hello, Golang!

bufio

入出力をバイト単位や行単位で行いたいときには bufio パッケージの関数が便利。リーダーやライターを作ってから、入出力関数を呼び出す。
バイト単位には、ReadByteWriteByte がいい。

10行目と11行目でリーダーとライターを作って、それを使っている。

^o^ > go build echo1.go

^o^ > echo1.exe < echo1.go
package main

import (
        "os"
        "io"
        "bufio"
)

func main() {
        r := bufio.NewReader(os.Stdin)
        w := bufio.NewWriter(os.Stdout)
        for {
                c, err := r.ReadByte()
                if err == io.EOF { break }
                w.WriteByte(c)
                if c == '\n' { w.Flush() }
        }
        w.Flush()
}

もうひとつ、行単位で処理するには、同じリーダー、ライターの ReadStringWriteString が使える。

^o^ > go build echo2.go

^o^ > echo2.exe < echo2.go
package main

import (
        "os"
        "io"
        "bufio"
)

func main() {
        r := bufio.NewReader(os.Stdin)
        w := bufio.NewWriter(os.Stdout)
        for {
                s, err := r.ReadString('\n')
                if err == io.EOF { break }
                w.WriteString(s)
                w.Flush()
        }
}

標準入出力

ファイル入出力はファイルディスクリプタを介して行う。要するにファイルをオープンしてファイルディスクリプタを取得し、使い終わったらクローズする、ってわけだ。
ただし、標準入出力に関しては最初から os パッケージに用意されている。

  • os.Stdin: 標準入力
  • os.Stdout: 標準出力
  • os.Stderr: 標準エラー出力

標準入力から読み込むには os.Stdin.Read 関数、出力するには os.Stdout.Write または os.Stderr.Write 関数を使う。
ちょっと試してみよう。次のプログラムは、標準入力から受け取った文字列をそのまま標準出力に書き出す(ctrl + C で終了)。

^o^ > go run echo.go
hello
hello

ひとつめの hello がキーボードからの入力で、ふたつめの hello が返ってきた出力だ。

二分探索木

今日は、練習のため二分探索木の写経をする。

 cf. 二分探索木 – M.Hiroi’s Home Page お気楽 Go 言語プログラミング入門

探索木自体は Item 型(インターフェイス)を対象としている。このため intInt という別名をつけて EqLess の2つのメソッドを定義している。わざわざ別名をつけているのは、通常の(構造体でない)型にはメソッドを定義できないため。

^o^ > go run binarytree.go
0 1 2 3 4 5 6 7 8 9
true
true
true
true
true
true
true
true
true
true
0 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
2 3 4 5 6 7 8 9
3 4 5 6 7 8 9
4 5 6 7 8 9
5 6 7 8 9
6 7 8 9
7 8 9
8 9
9

インターフェイスの埋め込み

構造体と同じく、インターフェイスも別のインターフェイスに埋め込むことができる。
下の例では、BazIFooIBarI を埋め込んでいる。すると、BazI を実装した構造体 Baz では、FooIBarI のメソッドを、あたかも自分のメソッドのように使えるようになる。

^o^ > go run interface_embed.go
1
10
0
0
2
20
1
2
0
0
3
4

型switch

データ型の判定は switch 文でもできる。これを型switchという。一般的な書式はこう。

一昨日書いた型アサーションと似ているけど、カッコの中には type と書く。すると対応する型の case 節が実行される。

^o^ > go run type_switch.go
6 6.6

空インターフェイスと型アサーション

空インターフェイスとは、定義すべきメソッドを持たないインターフェイスのことだ。言い換えるとどんな型にも当てはまる。空インターフェイスの配列やスライスはどんな型でも格納することができる。下の例ではスライス a に整数、実数、文字列を格納している。
Go は静的型付け言語なので、スライスに格納されているのが何の型なのかわからないのでは都合が悪い。そこで、型をチェックする機能がある。それが型アサーションだ。型アサーションは「変数.(型)」という書き方をして、変数が型と一致すればその値と true の2つの値が返ってくる。一致しなければゼロ値と false だ。下の例では、この型アサーションを使って、スライスに含まれる整数と実数のそれぞれの合計を求めている。

^o^ > go run interface3.go
6
6.6

インターフェイス

インターフェイスとは、異なる型の構造体を同じ型として扱うための抽象型のようなものだ。Java のインターフェイスと同様らしいけど Java のことはよく知らない。インターフェイスにはメソッドの型だけが定義されていて、そのメソッドすべてを実装した構造体はインターフェイスの型として扱えるようになる。

次の例では、インターフェイス Point を定義している。Point 型は distance0 というメソッドを持っている。Point2dPoint3d というのが具体的な構造体で、そのどちらもが distance0 メソッドを実装しているので、Point 型として1つのスライスに格納されている。

^o^ > go run interface.go
0
14.142135623730951
0
17.320508075688775
31.462643699419726