Golangを触り始めた② 文法・型
前回に引き続き、Web+DB vol.82のGo特集が分かりやすいのでそれ見ながら。
ぜんぶ網羅して書いてないけど、メモ。Goのバージョンは1.3.3です。
変数
var message string = "Hello world"
変数宣言は、var
で始まり、変数名、型の順。ちょっと違和感。
関数内部で変数宣言と初期化を行う場合は、以下のように:=
を利用しても書ける。こっちの書き方を使うことが多そう。変数の型は、代入する値から推論される。
/* import文は省略 */ func main() { message := "Hello world" fmt.Println(message) }
定数は、var
じゃなくてconst
で宣言。
if
条件部分に()
は付けないが、処理部分は{}
が必要。
func main() { a, b := 10, 1 if a > b { fmt.Println("aの方がデカイ") } }
for
func main() { for i := 0; i < 10; i++ { fmt.Println(i) } }
while
やloop
は無いので、for
で代替。
n := 0 for n < 10 { fmt.Println(n) n++ }
ループから抜けるのはbreak
、次の繰り返しに行くのはcontinue
。
switch
func main() { for n := 0; n < 31; n++ { switch { case n%15 == 0: fmt.Println("FizzBuzz") case n%5 == 0: fmt.Println("Buzz") case n%3 == 0: fmt.Println("Fizz") default: fmt.Println(n) } } }
関数
関数はfunc
で宣言。引数がある場合は、変数と合わせて型も指定。戻り値がある場合は、引数の次に書き、型を指定。
func sum(i, j int) int { return i + j } func main() { n := sum(1, 2) fmt.Println(n) // 3 }
関数は複数の値を戻り値として返すことができる。その際、戻り値を格納する変数を必要数分用意してあげないといけない。用意する変数の数が合わないとコンパイルエラー。無視したい戻り値があるときは、_
を利用して無視することを明示する。
func swap(i, j int) (int, int) { return j, i } func main() { x, y := 20, 30 x, y = swap(x, y) fmt.Println(x, y) // 30, 20 x, _ = swap(x, y) fmt.Println(x) // 20 }
関数でエラーを返す
上記のような複数の値を戻り値として返す特徴を利用し、Golangでは内部で発生したエラーを戻り値で表現する。
また、エラーはerrorsパッケージを使って自作できる。
// main.go package main import ( "errors" "fmt" "log" ) func div(i, j int) (int, error) { if j == 0 { return 0, errors.New("divided by zero") } return i / j, nil } func main() { n, err := div(10, 0) if err != nil { log.Fatal(err) } fmt.Println(n) }
上記を実行すると、こんな感じになる。
$ go run main.go 2014/10/26 15:29:31 divided by zero exit status 1
配列
Golangの配列は固定長。 例えば、長さが3で要素の型がstringだったら、こう書く。
arr := [3]string{"a", "b", "c"} // または arr := [...]string{"a", "b", "c"}
配列型は長さも情報として持っているため、長さが異なると別の型と認識される。
func show(arr [4]string) { fmt.Println(arr) } func main() { var arr1 [4]string var arr2 [5]string fn(arr1) fn(arr2) // コンパイルエラー! }
スライス
こっちが慣れ親しんでいる可変長配列。
var s []string
このように長さの情報を宣言に含めないで書くと、スライスになる。
append
スライス(の末尾)に値を追加をするにはappend
を利用。
var s []int s = append(s, 1, 2) fmt.Println(s) // [1 2]
range
配列やスライスに格納された値を先頭から順に処理するときは、range
を利用。
arr = [3]string{"a", "b", "c"} for i, s := range arr { fmt.Println(i, s) // iが添字、sが値 }
上記のような使い方はRubyのeach_with_index
的な感じかな。
値の切り出し
s := []int{1, 2, 3, 4, 5} fmt.Println(s[2:4]) // [3 4] fmt.Println(s[:3]) // [1 2 3] fmt.Println(s[3:]) // [4 5]
引数として使うとき
以下のように書くと、任意の長さのスライスを引数として使える
func sum(nums ...int) (result int) { for _, n := range nums { result += n } return } func main() { fmt.Println(sum(1, 2, 3, 4)) // 10 }
マップ
Rubyでいうところのハッシュ的なやつ。以下のように宣言。
var month map[int]string = map[int]string{} month[1] = "January" month[2] = "February"
宣言と初期化を同時にやるならこう書く。
month := map[int]string { 1: "January", 2: "February", } fmt.Println(month) // map[1:January 2:February]
値へのアクセス
Rubyなどと方法は同じ。
jan := month[1] fmt.Println(jan) // January
ただし、2つめの戻り値も実はある。 (このケースは変数を1つしか受け取らなくてもコンパイルエラー出ないのね)
2つめの戻り値は、指定したキーがこのマップに存在しているかを表すbool値。
_, ok = month[1] if ok { // データが存在したとき、何らかの処理 }
マップからデータ消すときはdelete
を使う。
delete(month, 1) fmt.Println(month) // map[2:February]
スライスと同様、range
で繰り返し処理を実現できるが、取り出される順番は保証されないらしい。。
ポインタ
ポインタ型の変数には、型の前に*
を付ける。アドレスは変数の前に&
をつけると分かる。Cと似ているらしい(触ったことない...)
func callByValue(i int) { i = 20 } func callByRef(i *int) { *i = 20 } func main() { i := 10 callByValue(i) fmt.Println(i) // 10 callByRef(&i) fmt.Println(i) // 20 }
defer
例えば、ファイル操作で利用するfile.Close()
(ファイルを閉じる作業)は必ず実行されなければならない。だが、関数の途中でパニックが起こったりすると、この文まで到達できない可能性がある。
そういったケースに使うのがdefer
。これを処理の前に書いておくと、main()を抜ける前にかならず実行されるようになる。
defer file.Close()
パニック
スライスの範囲外にアクセスしようとした時など、エラーを戻り値として返せない時、パニックという方法でエラーが発生する。
パニックは、recover()
で取得し、その中でエラー処理をする。recover()
をdefer
の中に書く。
// main.go func main() { defer func() { err := recover() if err != nil { log.Fatal(err) } }() a := []int{1, 2, 3} fmt.Println(a[10]) }
$ go run main.go 2014/10/26 16:33:39 runtime error: index out of range exit status 1
panicは自分でも発生させられる。
// main.go a := []int{1, 2, 3} for i := 0; i < 10; i++ { if i > len(a) { panic(errors.New("index out of range")) } fmt.Println(a[i]) }
$ go run main.go 1 2 3 panic: runtime error: index out of range goroutine 16 [running]: runtime.panic(0xb7e60, 0x142b5c) /usr/local/Cellar/go/1.3.3/libexec/src/pkg/runtime/panic.c:279 +0xf5 (以下略)
Goで書かれたソースをほとんど読んだことないので、使いどころのイメージがまだあまりできない。。エラーは、できるだけ戻り値として表現し、無理なときにpanicにするって感じだろうか。
構造体
Rubyのクラス的なやつ。type
を用いて宣言。
構造体のあとにフィールドを記述する。フィールドは、名前が大文字から始まればパブリック、小文字からならパッケージ内のみから見える。
type Task struct { ID int Detail string done bool } func main() { task := Task{ ID: 1, Detail: "Write a code", done: true, } fmt.Println(task.ID) // 1 fmt.Println(task.Detail) // "Write a code" }
taskには構造体が代入され、各フィールドにはドットでアクセスできる。
ポインタ型として扱うことも可能。
new()
new()
で構造体の初期化を行える。フィールドはすべてゼロ値となる。
func main() { var task *Task = new(Task) fmt.Println(task.done) // false }
コンストラクタ
Newではじまる関数を定義し、その中で構造体を生成することで、コンストラクタの役割を果たす。
func NewTask(id int, detail string) *Task { task := &Task{ ID: id, Detail: detail, done: false, } return task } func main() { task := NewTask(1, "Write a code") fmt.Printf("%+v", task) // &{ID:1 Detail:Write a code done:false}% }
メソッド
型にはメソッド定義ができる。 以下は、レシーバをポインタとして受け取るケース。
func (task *Task) Finish() { task.done = true } func main() { task := NewTask(1, "Write a code") task.Finish() fmt.Printf("%+v", task) // &{ID:1 Detail:Write a code done:true}% }
ポインタを使っているので、レシーバのdoneの値がtrueに変わっている。
インターフェース
インターフェースは、型がどのようなメソッドを実装すべきかを規定する。
type Finisher interface { Finish() }
こんなかんじで、関数名にerを付けて書くのがお作法らしい。
型の埋め込み
Goには、継承という概念が無いみたい。代わりに、「他の型を埋め込む」という形で構造体やインターフェースを拡張できる。
以下、構造体の埋め込み例。
// main.go package main import "fmt" type User struct { FirstName string LastName string } func (u *User) FullName() string { fullname := fmt.Sprintf("%s %s", u.FirstName, u.LastName) return fullname } func NewUser(firstName, lastName string) *User { return &User{ FirstName: firstName, LastName: lastName, } } type Task struct { ID int Detail string done bool *User } func NewTask(id int, detail, firstName, lastName string) *Task { task := &Task{ ID: id, Detail: detail, done: false, User: NewUser(firstName, lastName), } return task } func main() { task := NewTask(1, "Write a code", "Yusuke", "Aono") fmt.Println(task.FirstName) fmt.Println(task.FullName()) fmt.Println(task.User) }
$ go run hello.go Yusuke Yusuke Aono &{Yusuke Aono}
Taskの構造宣言時に、Userという型を埋め込む。これでTaskからUserのフィールドやメソッドにアクセスできるようになる。
おわり
お腹がすいたので、おわり。 Ruby書いてる時と異なり、留意しなきゃいけない点が多そうではある。ま、性質の違う言語なんだから当たり前か。。
エラー処理のあたりは難しそう(書くのめんどくさそう)な印象だが、もう少し書いてみないと判断できないですね。