dev blog

プログラミングめも

Go言語|Basics

Go言語の基礎メモ。

関数

func add(x int, y int) int {
    return x + y
}

引数の型が同じ場合は、最後の引数の型だけ書いてもOK。

func add(x, y int) int {
    return x + y
}

関数の中では、 var 宣言の代わりに := の代入文を使って暗黙的な型宣言ができる。

func main() {
    var i, j int = 1, 2
    k := 3
    c, python, java := true, false, "no!"

    fmt.Println(i, j, k, c, python, java)
}

// 1 2 3 true false no!

For文

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    fmt.Println(sum)
}

// 45

Defer

defer に渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させることができる。

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

    fmt.Println("hello")
}

// hello
// world

ポインタ

& オペレータは、そのオペランド(operand)へのポインタを引き出す。
* オペレータは、ポインタの指す先の変数を示す。

i := 42
p = &i 
fmt.Println(*p) // ポインタpを通してiから値を読みだす -> 42

*p = 21 // ポインタpを通してiへ値を代入する
fmt.Println(i) // 21

Goでは関数呼び出しの時に、引数や戻り値のコピーを行う。
このとき変数をポインタにしていた場合は、そのポインタが指すアドレス値をコピーするため、コピーコストを抑えられる。

構造体

構造体内で宣言された変数をフィールドと呼ぶ。 頭文字が大文字だとpublicに、小文字だとprivateになる。

// public
type Vertex struct {
    X int
    Y int
}

// private
type vertex struct {
    X int
    Y int
}

配列

intの10個の配列。
配列の長さは型の一部分なので、配列のサイズを変えることはできない。

var a [10]int

スライス

スライスは配列への参照のようなもの。
スライスはどんなデータも格納しておらず、単に元の配列の部分列を指し示す。

primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] // primesの1から4を含むスライスを作成
fmt.Println(s)

// [3 5 7]

配列の作成とそれを参照するスライスを同時に作成できる。

array := [3]bool{true, true, false} // これは配列
slice := []bool{true, true, false} // 上記と同じ配列を作成し、それを参照するスライスを作成
fmt.Println(array)
fmt.Println(slice)

// [true true false]
// [true true false]

これは上記と同様の配列を作成し、それを参照するスライスを作成

Map

Key と Value

m := make(map[string]int)
m["Answer"] = 42
fmt.Println(m["Answer"])

// 42

メソッド

Goにはクラスの仕組みが無い。
構造体(Vertex)にメソッド(Abs)を定義できる。
データと処理を強く結びつけたいときに多用される。

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}

Absメソッドの引数 v を、レシーバと呼ぶ。

レシーバにポインタを使うこともできる。
ポインタレシーバを使う理由は二つ。

  • ポインタレシーバが指す先の変数を、メソッド内で変更するため。
  • メソッドの呼び出し毎に変数のコピーを避けるため。レシーバが大きな構造体である場合に効率的。
type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    fmt.Printf(v.Abs())
    v.Scale(5)
    fmt.Printf(v.Abs())
}

// 5
// 25

Goroutine

ゴルーチン。Goのランタイムに管理される軽量なスレッド。

go f(x, y, z)

と書くと、新しいgoroutineが実行される。

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world") // 別のゴルーチンで実行される
    say("hello") // go say の完了を待たずに実行される
}

// hello
// world
// hello
// world
// world
// hello
// hello
// world
// hello

チャネル

チャネルを使うと、複数のゴルーチン間で値の送受信ができる。

あるゴルーチンから別のゴルーチンへ値を渡したいときは、チャネルへその値を送信する。

ch := make(chan int)
ch <- 100

あるゴルーチンから渡された値を受け取るときは、チャネルを利用して受信する。

n := <- ch