はじめてのGo
基本はA Tour of Goに沿っていく
Goにwhile、foreachなどはなくて、繰り返しはforのみ。
Defer
defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
defeが複数宣言された場合は、LIFO(後入れ先出し)の順番で実行されていく。
細かい挙動は以下。
ポインタ
ポインタ = 値のメモリアドレス
ポインタ型の宣言
var p *int
ポインタ型pに変数iのポインタを代入
i := 77
p = &i
ポインタ型pの値を出力。そしてポインタ型pの値に21を代入。
fmt.Println(*p)
*p = 21
まだ用途がわからん^^
構造体
構造体は、フィールドの集まり。
package main
import "fmt"
type Person struct {
age int
name string
}
func main() {
fmt.Println(Person{23, "shira"})
}
{23 shira}
を出力。
フィールドには.
でアクセス
func main() {
me := Person{23, "shira"}
fmt.Println(me.age)
}
構造体のポインタp がある場合、フィールドXにアクセスするには (*p).X のように書くことができます。 しかし、この表記法は大変面倒ですので、Goでは代わりに p.X と書くこともできます。
func main() {
me := Person{23, "shira"}
p := &me
p.age = 24
// (*p).age = 24 と同義
fmt.Println(me)
}
配列の長さは、型の一部分です。ですので、配列のサイズを変えることはできません。 これは制約のように思えますが、心配しないでください。 Goは配列を扱うための便利な方法を提供しています。
Goにおいて配列は固定長。スライスが可変長にあたる。
配列は名前付きフィールドではなく、インデックス付きのフィールド
配列の指定
var hoge [5]string
スライスの指定
var huga []string
スライスは配列への参照のようなものです。どんなデータも格納しておらず、単に元の配列の部分列を指していて、スライスの要素を変更すると、その元となる配列の対応する要素も変更される。
同じ元となる配列を共有している他のスライスは、それらの変更が反映される。
スライスは、配列のセグメントを表す記述子です。配列へのポインタ、セグメントの長さ、およびその容量(セグメントの最大長)から構成されます。
スライスは配列を参照している。元の配列の長さを越える、つまりスライスの容量を越える場合は、、appendでより大きいサイズの配列を割り当て直す。
Range
for ループに利用する range は、スライスや、マップ( map )をひとつずつ反復処理するために使います。
スライスをrangeで繰り返す場合、rangeは反復毎に2つの変数を返します。 1つ目の変数はインデックス( index )で、2つ目はインデックスの場所の要素のコピーです。
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 128}
func main() {
for i, v := range pow {
fmt.Printf("%d => %d\n", i, v)
}
}
インデックスや値は、 " _ "(アンダーバー) へ代入することで捨てることができます。
map
PHPにおける連想配列のイメージ。
package main
import "fmt"
type Person struct {
age int
name string
}
func main() {
var m = map[string]Person{
"me" : {age: 23, name: "shira"},
"you" : {age: 25, name: "tarou"},
}
fmt.Println(m)
}
make 関数は指定された型のマップを初期化して、使用可能な状態で返します。
mapに値が存在しない場合は要素のゼロ値になる。
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Answer"] = 42
m["Answer2"] = 48
fmt.Println( m["Answer"], m["Answer2"])
delete(m, "Answer2")
fmt.Println( m["Answer"], m["Answer2"], m["Answer33"])
}
キーに対する要素が存在するかどうかは、2つの目の値で確認する。存在するかどうか、boolで受け取る。
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
関数値
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
Powはべき乗の関数で、2つのfloat64を引数に取る。
Goの関数は クロージャ( closure ) です。 クロージャは、それ自身の外部から変数を参照する関数値です。 この関数は、参照された変数へアクセスして変えることができ、その意味では、その関数は変数へ"バインド"( bind )されています。
クロージャー
x,yを初期化した上で、クロージャーを返してる。
func fibonacci() func() int {
のfunc() int {
は、「返り値がintの関数」が返り値になるという意味。
クロージャーでフィナボッチ実装
package main
import "fmt"
// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
x := 0
y := 1
return func() int{
result := x
x = y
y = result + y
return result
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
メソッド
Goには、クラス( class )のしくみはありませんが、型にメソッド( method )を定義できます。
メソッドは、特別なレシーバ( receiver )引数を関数に取ります。
レシーバは、 func キーワードとメソッド名の間に自身の引数リストで表現します。
いまここ
// ポインタレシーバーのメソッド
// ポインタレシーバーであれば、レシーバそのものにを更新できる
func (v *Vertex) Scale1(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
// 変数レシーバーのメソッド
// 変数レシーバーは、関数の引数と同じふるまいで、レシーバのコピーを操作する
func (v Vertex) Scale2(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
ポインタレシーバを使う2つの理由があります。
ひとつは、メソッドがレシーバが指す先の変数を変更するためです。
ふたつに、メソッドの呼び出し毎に変数のコピーを避けるためです。 例えば、レシーバが大きな構造体である場合に効率的です。
例では、 Abs メソッドはレシーバ自身を変更する必要はありませんが、 Scale と Abs は両方とも *Vertex 型のレシーバです。
一般的には、値レシーバ、または、ポインタレシーバのどちらかですべてのメソッドを与え、混在させるべきではありません。
interface(インタフェース)型は、メソッドの一覧を定義したデータ型
インタフェースを実装することを明示的に宣言する必要はありません( "implements" キーワードは必要ありません)。Interfaceが期待するメソッドをすべて満たした変数には、自動的にInterfaceが実装されます。
もっともよく使われているinterfaceの一つに fmt パッケージ に定義されている Stringer があります
type Stringer interface {
String() string
}
string()を宣言すると、勝手にstringer interfaceが実装されることになる。
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
}
stringメソッドで出力の形式を上書きできるような仕組みになっているが、これは、stringメソッドが宣言されていれば、stringerのinterfaceを実装していることになるので、型で分岐してる。
print.go
ここにログを残した
go mod 初期設定
go mod init $(basename `pwd`)
go mod tidy はソースコードを検査して、どのような外部パッケージを利用しているかを判定します。
go mod tidy
外部パッケージのダウンロード
go mod download
Go プログラム(パッケージ)を実行した際は、依存パッケージの読み込み > グローバル定数 > グローバル変数 > init() > main() の順に実行(判定)されていく。