🚶‍♂️

Goの基本のキ

2022/11/13に公開

Tour of Goの内容メモ
手元に実行環境も用意しているが、オンラインでも用意されているのはありがたい。

Imports | パッケージの参照

import "fmt"

import (
  "fmt"
  "math/rand"
)

Functions | 関数

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

引数となる変数名の後ろに型名を書く。

同じ型の引数が複数ある場合は、最後の型を省略する記述が可能。

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

関数は複数の戻り値を返すこともできる

func swap(x, y string) (string, string) {
  return y,x
}

戻り値となる変数に名前を付けることも可能(named return value)
戻り値に名前を付けると、関数の最初で定義した変数名として扱われる。
また、名前を付けた戻り値を使うと、 return ステートメントに何も書かずに戻すことが可能。(これを naked returnと呼ぶ)
naked return は、短い関数でのみ利用すべきらしい。長い関数で使うと可読性が悪くなるため非推奨。

func split(sum int) (x, y int) {
  x = sum * 4 / 9
  y = sum - x
  return
}

Variable | 変数

変数の宣言には var ステートメントを使用する。

var c, python, java bool

func main() {
  var i int
  fmt.PrintLn(i, c, python, java)
}

初期化も行う場合、型を省略することができる。その変数は初期化子が持つ型と同じになる。

var i, j int = 1, 2
var c, python, java = true, false, "no"

関数の中でのみ、省略形 := の代入文を使って、型宣言が可能。

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

Basic Types | Goの基本の型

  • bool
  • string
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64, uintptr
  • byte (uint8の別名)
  • rune (int32の別名。Unicodeのコードポイントを表す)
  • float32, float64
  • complex64, complex128

int, uint, uintptr 型は32-bitシステムでは32bit, 64-bitシステムでは64bitで扱われる。
特別な理由がない限り、整数の変数が必要な場合は int で良い。

変数に初期値を与えずに宣言するとゼロ値が与えらられる。

  • 数値型の場合(int, float): 0
  • bool型: false
  • string型: "" (空文字列)

Type Conversions | 型変換

変数 v を 型 T に変換したい場合、T(v) と記述することで変換ができる。

var i int = 42
var f float64 = float64(i)
var u int = uint(f)

C言語とは異なり、明示的な変換が必要。
明示的に変換せずに別の型の変数に代入しようとすると、以下のようなエラーが表示される。

package main

import (
	"fmt"
)

func main() {
	var i int = 42
	var f float64 = i
	fmt.Println(i,f)
}

> ./prog.go:9:18: cannot use i (variable of type int) as type float64 in variable declaration

Constants | 定数

Goで定数を扱いたい場合は、 constキーワードを使用する。
定数は、文字(character)、文字列(string)、boolean、数値(numeric)のみ使える。
また、定数は := を使って宣言することはできない。

const World = "世界"
const Truth = true

For

forループは、初期化、条件式、後処理をセミコロンで区切って記述する。

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

初期化と後処理は任意。書かなくても動作する。

func main() {
    sum := 1
    for ; sum < 1000; {
        sum += sum
    }
    fmt.Println(sum)
}

セミコロンを省略することもできる。この書き方で while文のような使い方が可能。

func main() {
    sum := 1
    for sum < 1000 {
        sum += sum
    }
    fmt.PrintLn(sum)
}

他の言語のように、条件部分を()で括る必要はない。括るとシンタックスエラーになる。
また、初期化の際に var を使った初期化は行えない。:=を使用する必要がある。

ループ条件を省略すれば、無限ループになる。

func main() {
    for {
    }
}

If

for と同様に括弧は不要。

func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

if ステートメントは、条件の前に、簡単なステートメントを書くことができる。
下の例では変数vが宣言されている。このvifのスコープ内だけで使用できる。
ifのスコープ外では vは使えない。

func sum2(x, y, limit int) int {
	if v := x + y; v < limit {
		return v
	}
	return 0
}

func main() {
	fmt.Println(
		sum2(1,2,10),
	)
}

> 3

ifelse の書き方

func sum3(x, y, limit int) int {
	if v := x + y; v < limit {
		return v
	} else {
    	return v+1
    }
    return 0
}

ifステートメントで宣言された変数は、elseブロックでも使用することができる。

Switch

switch ステートメントは、if-elseステートメントのシーケンスを短く方法。
Goのswitchは、選択されたcaseだけを実行して、それに続く他の全てのcaseは実行されない。
他の言語では、caseの最後に必要な break ステートメントがGoでは不要になる。(自動的に提供される)
breakを明示的に記述してもエラーは起きず、実行可能。
また、Goのswitchのcaseは定数や整数である必要がない。

func choice(name string) {
	switch s := name; s {
	case "apple":
		fmt.Println("apple select")
	case "banana":
		fmt.Println("banana select")
	case "orange":
		fmt.Println("orange select")
	default:
		fmt.Println("no select")
	}
}

switchは、上から下へと評価される。caseの条件が一致すれば、そこで終了する。

条件のないswitchは switch true と書くことと同じ。
この構造により、if-then-elseをシンプルに表現することができる。

t := time.Now()
switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
}

余談だがTour of Goでは、switchステートメントとcaseステートメントのインデントが同じ階層にある。もちろん実行可能だが、caseステートメントはswitchの {} の中で使われてるのでインデントさせたほうが気持ちがいい。インデントしてもエラーなく動作する。

Defer

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

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

	fmt.Println("hello")
}

> hello
> world

deferへ渡した関数が複数ある場合はスタックされる。渡された関数はLIFO(最後に入れたものが最初に出る)の順番で実行される。

fmt.Println("counting")

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

fmt.Println("done")

> counting
> done
> 9
> 8
> 7
> 6
> 5
> 4
> 3
> 2
> 1
> 0

deferのより詳細な使い方は、こちらの記事に誘導されていた。

Discussion