Open10

Goの勉強ログ

ピン留めされたアイテム
DaikiSuyamaDaikiSuyama

目的

  • サーバサイド言語としてのGoの勉強

期間

  • 6月中旬ごろ~

内容(予定を含む)

ProgateのGoコース

  • Goの概要を把握する

ローカルの環境構築

  • Goの環境を快適にする

A Tour of Go

  • 基本文法を頭に入れる

AtCoder

  • 基本文法を使いこなす

Web開発実践入門

  • GoによるWeb開発のイメージを持つ
  • Web系の知識を定着させる
  • わざわざGoでやる必要はない?

フレームワークのチュートリアル

  • Revelなど複数あるが体系的には難しそう
  • JavaScriptの方が勉強しやすそう

備考

はてなブログに投稿していたものを移行

DaikiSuyamaDaikiSuyama

日付

  • 6/23
  • 開発日記~1日目~

概要

  • A Tour of Goでの基本文法の把握
    • 40分程度
    • BasicsのPackages,variables, and functionsの7まで

内容

Packages

  • Goのプログラムは「パッケージ」で構成される
    • importすることで他のパッケージを使用可能
  • プログラムはmainパッケージから開始
  • importのパスの最後の要素はパッケージ名と一致
    • e.g. "math/rand"は「package rand」で始まるパッケージを示す

Imports

  • 括弧でまとめても分けてもimport可能
import ( "fmt" "math" )

import "fmt"
import "math"

Exported names

  • パッケージでエクスポートされた名前はインポート元のファイルで使用可能
    • 識別子を大文字にすることでエクスポート可能

Functions

  • (1)funcで関数を宣言&定義
    • 型名は変数の後ろにおいて宣言→why
  • (2)型が同じ場合はまとめて宣言可能
  • (3)戻り値が複数あってもOK(戻り値の型は括弧で括る)
  • (4)戻り値に予め名前をつけることも可能
    • 関数のドキュメントとして用いることが好ましい
    • returnの後に何も書かないでも大丈夫("naked" return)
    • 可読性は下がるので避ける
//(1)
func add(x int, y int) int {
	return x + y
}

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

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

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

日付

  • 6/24
  • 開発日記~2日目~

概要

  • A Tour of Goでの基本文法の把握
    • 35分程度
    • BasicsのPackages,variables, and functionsの残り

内容

Variables

  • 変数宣言時にvarを用いる(1)
    • 宣言時にゼロ初期化
    • 数値型は0、bool型はfalse、string型は空文字列
  • 宣言時に初期化可能(2)
    • その場合は型を省略可能
    • 初期化子の型になる
  • 関数内では暗黙的型宣言が可能(3)
    • varを使わずに:=を利用するだけ
    • ただし、関数外では暗黙的型宣言はできないことに注意
//(1)
var c, python, java bool
//(2)
var c, python, java = true, false, "no!"
//(3)
func main() {
  c, python, java := true, false, "no!"
}

Type

  • 組み込み型(基本型)は下記に列挙
    • 他の言語とそこまで変わらない
  • 変数の暗黙的型変換はできない(1)
    • 明示的な変換が必要
  • 明示的な型を指定していない変数宣言の場合は右側の変数からの型推論(2)
    • 型を指定していない数値の場合は、int,float64,complex128のいずれかになる。
bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名(Unicode のコードポイント)

float32 float64

complex64 complex128

//(1)
i := 42
f := float64(i)
u := uint(f)

//(2)
i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Constants

  • 定数の宣言はvarの代わりにconstを用いる
    • 文字、文字列、真偽値、数値、で使うことができる
    • :=を使った宣言をすることはできない
DaikiSuyamaDaikiSuyama

日付

  • 6/26
  • 開発日記~3日目~

概要

  • A Tour of Go
    • 25分程度
    • BasicesのFlow control statementsのSwitchまで

内容

For

  • セミコロンで3つの部分に区切る(1)
    • 初期化ステートメント:初めのイテレーションの前に初期化
    • 条件式:イテレーション毎に評価(falseでイテレーションを停止)
    • 後処理ステートメント:イテレーション毎の最後に実行
    • ()で3つの部分を括る必要はない
    • それぞれのステートメントの記述は任意
  • while文はない(2)
    • for文で条件式のみを指定することで、while文として用いることができる
    • 条件式のみの場合はセミコロンも省略可能
  • 条件式も省略すると、無限ループになる(3)
//(1)
for i := 0; i < 10; i++ {
  sum += i
}

//(2)
for sum < 1000 {
  sum += sum
}

//(3)
for {

}

If

  • 他の言語と同じ記法、ただ()は必要ない
  • 条件の前に評価のためのステートメントを書くことができる(1)
    • 宣言された変数はif文のスコープ内でのみ有効
    • elseブロック内でもその変数は有効
//(1)
func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

Exercise: Loops and Functions

  • 指示に従って実装するのみで特に詰まるところはなし

Switch

  • 一般的なswitch文と記法は同じ(1)
    • 選択されたcaseのみが実行されるため、その後は実行されない
    • それぞれのcaseでbreakが自動実行されていると考えると良い
    • また、caseには変数を使用しても良い
  • 条件のないswitch文
    • 条件を省略することも可能で、この場合は条件がtrueであることと同義
    • 「if-then-else」をシンプルに表すことができる!
//(1)
func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

//(2)
func main() {
	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.")
	}
}
DaikiSuyamaDaikiSuyama

日付

  • 6/27
  • 開発日記~4日目~

概要

  • A Tour of Go
    • 1時間程度
    • Flow control statementsは終了

内容

Defer

  • deferとは(1)
    • 渡した関数の実行を呼び出し元の関数の処理が終了するまで遅延させるステートメント
    • 渡した関数の引数はすぐに評価されるが、その関数の処理自体は呼び出し元の関数のreturnの後に実行される
  • 複数の関数をdeferに渡す場合(2)
    • 呼び出しはスタックされる
    • LIFOの順番で実行される
//(1)
func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

//(2)
func main() {
	fmt.Println("counting")

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

Pointers

  • 特徴
    • 変数Tのポインタは*T
    • ゼロ値はnil
    • &はオペランドへのポインタ
    • *はポインタの指す変数自体
    • C++とあまり変わらないが、ポインタ演算はない!

Structs

  • 構造体
    • フィールドの集まり
    • .を使ってフィールドにアクセスする
  • 構造体へのポインタ(1)
    • フィールドXを持つ構造体のポインタpがある場合、(*p).Xと書くことができる
    • 代わりにp.Xとして省略することもできる
  • 構造体の初期化(2)
    • {}でフィードの値を囲む
    • フィールドの値を列挙することで順に割り当てることができる(2.1)
    • 一部の値のみを指定することができる(2.2)
    • 指定されていない値はゼロ初期化される(2.3)
//(1)
type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	(*p).X = 1e9
	fmt.Println(v)
}

//(2)
type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // has type Vertex (2.1)
	v2 = Vertex{X: 1}  // Y:0 is implicit (2.2)
	v3 = Vertex{}      // X:0 and Y:0 (2.3)
	p  = &Vertex{1, 2} // has type *Vertex (ポインタを返す)
)

Arrays

  • 配列
    • [n]T型:型Tのn個の配列を表す
    • 配列は固定長
    • 初期化の際は{}で囲む
func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}

Slices

  • 可変長の配列(のようなもの)
    • []T型:型Tのスライス
    • 配列の参照にあたり、元の配列の部分列を指し示す
    • スライスの要素を変更すると、元の配列の対応する要素も変更される
    • 同じ元の配列を共有しているスライスどうしの変更はお互いに反映される
  • スライスの初期化
    • 配列をスライスするパターン(1)
    • 配列と同様のパターン:可変長と同様の扱いをできる(2)
  • スライスの表記の省略
    • Pythonと同じ
  • スライスの持つ属性(3)
    • 長さと容量(可変長配列なので)
      • 長さ:論理的な量
      • 容量:物理的な量
    • 謎の挙動しているので疑問
      • とりあえずの結論は出た?(10分調べて)
      • s[low,high]とした時を考える
        • lowを指定する→lowより前は参照できなくなる、lengthとcapacityもその分小さくなる
        • highを指定する→highよりも後ろも参照できる、lenghtだけその分小さくなる
        • 範囲外(capacityよりも外側)を指定すると、エラーが出る
  • スライスのゼロ値
    • nil
    • 長さも容量も0の空配列(元となる配列を持たないという解釈の方が正しいかも)
  • make関数
    • 組み込みのmake関数(4)
    • make(配列の型,長さ,容量):第三引数は省略可能、省略しない場合は長さ=容量
  • スライスに含まれる型
    • スライスには任意の型を含めることができるので、多次元スライスも可能
  • スライスへの追加(append関数)(5)
    • func append(s []T, vs ...T) []T
    • 追加元のスライスsに加えるvs(T型の変数群)を加えたものを返す
    • 元の配列の容量が足りない場合はより大きい容量の配列を割り当てし直す
//(1)
func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

//(2)
func main() {
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(q)

	r := []bool{true, false, true, true, false, true}
	fmt.Println(r)
}

//(3)
func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

//(4)
func main() {
	a := make([]int, 5)
	printSlice("a", a)

	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

//(5)
func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s)
}
DaikiSuyamaDaikiSuyama

別のオブジェクト指向言語やってからGoに来ると何が良いんだい?
自分なりに感じたのは

  • 基底型との間でしかis-aの関係がなく、基本的には型同士の関係は部分型でhas-aとして定義される
    • 継承の難しさがほぼほぼ消える
    • is-aとhas-aの分離
  • 部分型はカプセル化しやすい、クラスよりも
  • 構造体とメソッドに分離される
    • 振る舞いと状態を分離できる
  • 菱形継承問題が比較的避けやすい
    • 適切に部分型として性質を分けやすいので