第二言語を学ぶ時期が来た
以前、勉強のために『プログラミング言語Go』を購入した。
最近、とある野望を抱いて「Go言語で実装してみよう」と思い立ったが、さすがにGo言語を全く知らない状態で実装を進めても無理があることがすぐにわかった。 そのため、積読状態であった『プログラミング言語Go』を読むことにした。 ただ読んでも継続できないと思ったので、ここのブログにエントリを書きこ残すことにする。
今回は1.1から1.3まで。
1.1 ハロー、ワールド
伝統のHello, World。
// helloworld.go package main import ( "fmt" ) func main() { fmt.Println("Hello, 世界") }
- 実行するには
go run helloworld.go
を実行。ただし、今回はJetBrainsのGoglandを使っているのでCtrl+F10
を押すか緑の矢印を押すだけ。 - 各ソースファイルは
package
宣言から始まる。続いてインポートするパッケージを並べる。この順序は必ず守る必要がある。さもなければexpected 'package', found 'import'
となる。 fmt
はフォーマットされた入出力を扱う函数があるパッケージ。main
パッケージは特別で、単独で実行可能なプログラムを定義する。- 函数宣言は予約語
func
、関数名、パラメータリスト(今回は空)、結果のリスト(今回は空)、本体からなる。 - 同じ行2つ以上の文や宣言が現れない限り、文や宣言の最後にセミコロンを必要としない。「事実上、ある種のトークンの後の改行はセミコロンへ変換されます。」とある。
- Goのコードを解析するためには改行する場所が問題となる。
gofmt
ツールでコードを標準書式へ書き換える。
1.2 コマンドライン引数
// echo1.go package main import ( "fmt" "os" ) func main() { var s, sep string for i := 1; i < len(os.Args); i++ { s += sep + os.Args[i] sep = " " } fmt.Println(s) }
os
パッケージはプラットフォームとは独立した形式でOSを扱うためのパッケージ。Pythonのos
と目的は同じと思われる。- コマンドライン引数は
os.Args
で扱う。os.Args
は文字列のスライスで動的に大きさが決まる配列要素のシーケンス。Pythonではsys.args
で配列であった。 os.Args[0]でコマンド自身の名前。
os.Args[1:len(os.Args)]`が引数。インデックスの指定は半開。- Goglandのフォーマットと、
gofmt
は微妙に動作が異なる。Goglandの設定を変更すれば保存時にgofmt
とgoimports
が実行される。 var
で変数宣言。変数が明示的に初期化されない場合は暗黙的に変数の型に対するゼロ値に初期化される。for
ループはGoで唯一のループ文。C言語風の文、範囲を指定するものがある。
// echo2.go package main import ( "fmt" "os" ) func main() { s, sep := "", "" for _, arg := range os.Args[1:] { s += sep + arg sep = " " } fmt.Println(s) }
効率の良い方法はstrings
のJoin
を使うことである。
//echo3.go package main import ( "fmt" "os" "strings" ) func main() { fmt.Println(strings.Join(os.Args[1:], " ")) }
1.3 重複した行を見つける
// dup1.go package main import ( "bufio" "fmt" "os" ) func main() { counts := make(map[string]int) input := bufio.NewScanner(os.Stdin) for input.Scan() { counts[input.Text()]++ } for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } }
if
の条件部分は丸括弧で囲まない。本体は波括弧で囲む。make(map[string]int)
で文字列がキーで整数値が値である空のマップを作成する。make
にはほかの用途もある(がまだ書かれていない)。- マップの繰り返しの順序は規定されていない。
input := bufio.NewScanner(os.Stdin)
で標準入力から読み込むスキャナができる。input.Scan()
を呼び出すごとに1行読み込み改行文字を取り除く。中身はinput.Text()
で取り出す。fmt.Printf
はC言語みたいなフォーマットされた文字列を出力する。
// dup2.go package main import ( "bufio" "fmt" "os" ) func main() { counts := make(map[string]int) files := os.Args[1:] if len(files) == 0 { countLines(os.Stdin, counts) } else { for _, arg := range files { f, err := os.Open(arg) if err != nil { fmt.Fprintf(os.Stderr, "dup2: %v\n", err) continue } countLines(f, counts) f.Close() } } for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } } func countLines(f *os.File, counts map[string]int) { input := bufio.NewScanner(f) for input.Scan() { counts[input.Text()]++ } }
os.Open
はファイル*os.File
とエラーの結果を返す。nil
と一致するかどうかで成功の可否を判定する。C言語っぽい。- このファイル処理はストリーム処理となる。
// dup3.go package main import ( "fmt" "io/ioutil" "os" "strings" ) func main() { counts := make(map[string]int) for _, filename := range os.Args[1:] { data, err := ioutil.ReadFile(filename) if err != nil { fmt.Fprint(os.Stderr, "dup3: %v\n", err) continue } for _, line := range strings.Split(string(data), "\n") { counts[line]++ } } for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } }
ioutil.ReadFile
はファイル全体を一気にメモリへ読み込む函数。楽であるが、ファイルが大きいと辛そう。