Open21

はじめてのGo

shira79shira79

Defer

defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。

package main

import "fmt"

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

defeが複数宣言された場合は、LIFO(後入れ先出し)の順番で実行されていく。

細かい挙動は以下。
https://qiita.com/Ishidall/items/8dd663de5755a15e84f2

shira79shira79

ポインタ

ポインタ = 値のメモリアドレス

ポインタ型の宣言

var p *int

ポインタ型pに変数iのポインタを代入

i := 77
p = &i

ポインタ型pの値を出力。そしてポインタ型pの値に21を代入。

fmt.Println(*p) 
*p = 21 

https://youtu.be/g2Cor6fjmNM

まだ用途がわからん^^

shira79shira79

構造体

構造体は、フィールドの集まり。

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)
}
shira79shira79

構造体のポインタp がある場合、フィールドXにアクセスするには (*p).X のように書くことができます。 しかし、この表記法は大変面倒ですので、Goでは代わりに p.X と書くこともできます。

func main() {
	me := Person{23, "shira"}
	p := &me
	p.age = 24
        // (*p).age = 24 と同義
	fmt.Println(me)
}
shira79shira79

配列の長さは、型の一部分です。ですので、配列のサイズを変えることはできません。 これは制約のように思えますが、心配しないでください。 Goは配列を扱うための便利な方法を提供しています。

Goにおいて配列は固定長。スライスが可変長にあたる。
配列は名前付きフィールドではなく、インデックス付きのフィールド

https://go.dev/blog/slices-intro

配列の指定

var hoge [5]string

スライスの指定

var huga []string

スライスは配列への参照のようなものです。どんなデータも格納しておらず、単に元の配列の部分列を指していて、スライスの要素を変更すると、その元となる配列の対応する要素も変更される。
同じ元となる配列を共有している他のスライスは、それらの変更が反映される。

スライスは、配列のセグメントを表す記述子です。配列へのポインタ、セグメントの長さ、およびその容量(セグメントの最大長)から構成されます。

https://go.dev/blog/slices-intro

スライスは配列を参照している。元の配列の長さを越える、つまりスライスの容量を越える場合は、、appendでより大きいサイズの配列を割り当て直す。

shira79shira79

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)
	}
}

インデックスや値は、 " _ "(アンダーバー) へ代入することで捨てることができます。

shira79shira79

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)
shira79shira79

関数値

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を引数に取る。
https://pkg.go.dev/math#Pow

Goの関数は クロージャ( closure ) です。 クロージャは、それ自身の外部から変数を参照する関数値です。 この関数は、参照された変数へアクセスして変えることができ、その意味では、その関数は変数へ"バインド"( bind )されています。

shira79shira79

クロージャー

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())
	}
}
shira79shira79

メソッド

Goには、クラス( class )のしくみはありませんが、型にメソッド( method )を定義できます。
メソッドは、特別なレシーバ( receiver )引数を関数に取ります。
レシーバは、 func キーワードとメソッド名の間に自身の引数リストで表現します。

いまここ
https://go-tour-jp.appspot.com/methods/1

shira79shira79
// ポインタレシーバーのメソッド
// ポインタレシーバーであれば、レシーバそのものにを更新できる
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
}
shira79shira79

ポインタレシーバを使う2つの理由があります。

ひとつは、メソッドがレシーバが指す先の変数を変更するためです。

ふたつに、メソッドの呼び出し毎に変数のコピーを避けるためです。 例えば、レシーバが大きな構造体である場合に効率的です。

例では、 Abs メソッドはレシーバ自身を変更する必要はありませんが、 Scale と Abs は両方とも *Vertex 型のレシーバです。

一般的には、値レシーバ、または、ポインタレシーバのどちらかですべてのメソッドを与え、混在させるべきではありません。

shira79shira79

interface(インタフェース)型は、メソッドの一覧を定義したデータ型

インタフェースを実装することを明示的に宣言する必要はありません( "implements" キーワードは必要ありません)。Interfaceが期待するメソッドをすべて満たした変数には、自動的にInterfaceが実装されます。

https://selfnote.work/20200716/programming/stringer-with-golang/

https://qiita.com/rtok/items/46eadbf7b0b7a1b0eb08

https://go-tour-jp.appspot.com/methods/17

もっともよく使われている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

shira79shira79

Go プログラム(パッケージ)を実行した際は、依存パッケージの読み込み > グローバル定数 > グローバル変数 > init() > main() の順に実行(判定)されていく。