Skip to content

Instantly share code, notes, and snippets.

@monochromegane
Created July 4, 2014 09:16
  • Star 24 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save monochromegane/8bb73390f2ebd9d325f4 to your computer and use it in GitHub Desktop.
速習Go。Fukuoka.go#1用の資料です。

速習Go

Go環境のつくりかた

MacOSX

homebrewでインストール

$ brew update
$ brew install go

環境変数GOPATHを設定する

$ export GOPATH=$HOME/go

Windows

Zip圧縮ファイル版が提供されています

zip

  • 展開先のディレクトリはC:\Goを推奨。
  • 上記以外に展開する場合は、該当ディレクトリを環境変数GOROOTに指定すること。
  • GOPATHはGOROOT/binを指定するとのこと

演習

以下のコマンドの出力結果を確認してみよう

$ go version
$ go env

Go開発環境のつくりかた

Vimを使っている場合は、fatih/vim-goプラグインを使うとGo開発に必要なものが一緒にインストールされて便利。

  • gocode
  • goimports
  • godef
  • oracle
  • golint
  • errcheck
  • gotags

Goを実行する

main.go

package main

import "fmt"

func main() {
	fmt.Println("Hello, Fukuoka.go")
}
$ go run main.go

ʕ◔ϖ◔ʔ < Go

変数

型を指定した宣言が基本だが、初期化も同時に行う場合は型を省略することができる。

// varを使った変数の宣言
var a int

// 初期値を与えることができる
var b int = 1

// 初期値があれば型は省略できる
var c = 2

// := を使った宣言と初期値設定
d := 3

fmt.Println(a, b, c, d)

ʕ◔ϖ◔ʔ < Go

演習

  • 宣言済の変数aに:=を使って値を設定できるか確認しよう
  • 宣言済の変数a, 未宣言の変数eに:=を使って値を設定できるか確認しよう
  • main関数外で:=を用いた変数宣言ができるか確認しよう

定数

// const を使った定数の宣言と初期化
const A int = 1

// 型を省略可能
const B = 1

// グルーピング宣言とiota列挙子
const (
	X = iota // 0
	Y        // 1
	Z        // 2
)

制御構文

for

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

// 条件文のみのfor
sum = 0
for sum < 100 {
	sum += sum
}
fmt.Println(sum)

// 無限ループ
for {
}

ʕ◔ϖ◔ʔ < Go

if

// if
var x int // 初期値は0
if x == 0 {
	fmt.Println("Zeroed")
}

// 条件式の前に文を書くことができる
if y := myFunc(); y == 0 {
	fmt.Println("Zero")
} else if y == 1 {
	// 宣言した値はここでも使える
	fmt.Println(y)
}

ʕ◔ϖ◔ʔ < Go

switch

// switch
switch runtime.GOOS {
case "darwin":
	fmt.Println("OS X") // breakは不要
default:
	fmt.Println("Other")
}

// caseには式を書くことができる
os := runtime.GOOS
switch { // 省略時は switch true と同じ
case os == "darwin":
	fmt.Println("OS X")
default:
	fmt.Println("Other")
}
  • breakは不要。明示的に次のcaseも評価させるときはfallthroughを使う

ʕ◔ϖ◔ʔ < Go

関数

関数の定義と実行

Go言語の関数は多値を返すことができる

func main() {
	// 関数の実行
	result := add(1, 2)
	fmt.Println(result)

	// 多値を受け取る関数の実行
	a, b := swap("hello", "fukuoka.go")
	fmt.Println(a, b)
	
	// errorが発生したか確認する
	result, err := div(2, 0)
	if err != nil {
		fmt.Printf("error: %s", err)
	}
}

// 関数の定義
func add(x int, y int) int {
        return x + y
}

// 多値を返すことができる
func swap(x, y string) (string, string) {
        return y, x
}

// error型を返す多値の例
func div(x, y int) (int, error) {
	if y == 0 {
		return 0, fmt.Errorf("divide by %d", y)
	}
	return x / y, nil
}
  • 関数の戻り値などで利用していない変数があるとコンパイルできないので注意
  • 多値のうち、使わないものがあればブランク識別子_に代入すること

ʕ◔ϖ◔ʔ < Go

First Class Functions

Goの関数は一級関数なのでリテラルを使って宣言した無名の関数を変数に格納することができます

func main() {
	// First Class Functions
	fn := func(s string) {
		fmt.Println(s)
	}
	fn("First Class Functions")
}

ʕ◔ϖ◔ʔ < Go

Closures

Goの関数は クロージャ( closure ) なので、関数の外部から変数を参照することができます

func main() {
	// Closures
	x := 5
	fn := func() {
		fmt.Println("x is", x)
	}
	fn() // x is 5
	x++
	fn() // x is 6
}
  • 外部の変数を利用できるのは便利だが、複数のGoルーチンから外部の変数を更新すると想定しない挙動になるので注意すること
  • そういった用途にはチャネルを利用する

ʕ◔ϖ◔ʔ < Go

ポインタ

Go言語では関数に対する値の受け渡しは値渡しとなり、値はコピーされます。 大きなサイズの値の受け渡しなどを避ける場合などポインタによる参照渡しを行います

// int型の変数iを宣言
i := 1

// int型のアドレスを格納できるポインタの型を宣言
var p *int

// アドレス演算子によって変数からアドレスを得る
p = &i

// 間接参照演算子によってアドレスから変数を得る
fmt.Println(*p) // 1

ʕ◔ϖ◔ʔ < Go

演習

  • pの値を変更することができるか確認しよう
  • *pの値を変更することができるか確認しよう

Go言語はクラスではなく「型」を持ちます。 型は値とメソッドを持つことができます

Go言語の基本型

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名
     // Unicode のコードポイントを表す

float32 float64

complex64 complex128

typeキーワードによる型の宣言

既存の型をもとに独自の型をつくることができます

// typeキーワードによる型の宣言
type myInt int

// 関数もtypeによる型宣言ができる
type myFunc func(x, y int) int

func main() {
	// myInt型の変数を使う
	var i myInt = 1
	fmt.Println(i + 1)
	
	// 関数リテラルで宣言したmyFunc型の関数を使う
	result := callMyFunc(func(x, y int) int {
		return x + y
	})
	fmt.Println(result)
}

func callMyFunc(fn myFunc) int {
	return fn(1, 2)
}
  • 関数型の宣言は関数の引数や返り値として利用することで定義をまとめたりストラテジーとして扱うと便利

ʕ◔ϖ◔ʔ < Go

構造体

構造体型は複数のフィールドと値を保持します。 メソッドを定義することでクラスのような使い方ができます。

// typeキーワードによる独自の構造体を宣言
type myStruct struct {
	i int
	s string
}

func main() {
	// フィールドの宣言順で初期化
	s1 := myStruct{1, "a"}

	// フィールド名を指定して初期化
	s2 := myStruct{i: 2, s: "b"}

	// ポインタの取得
	p := &myStruct{1, "a"}

	// フィールドへのアクセス
	fmt.Println(s1.i, s1.s)
	fmt.Println(s2.i, s2.s)
	fmt.Printf("p: %s", p)
}

ʕ◔ϖ◔ʔ < Go

メソッド

型をレシーバとすることで型に対してメソッドを定義することができます

// レシーバとなる型を指定する
func (s myStruct) print() {
        // レシーバ変数を使ってフィールドにアクセスすることができる
        fmt.Println(s.i, s.s)
}

// レシーバにポインタを使うことでフィールドの値を変更できる
func (s *myStruct) setInt(value int) {
        s.i = value
}

func main() {
	s := myStruct{1, "a"}
	s.print()
	s.setInt(2)
	s.print()
}

ʕ◔ϖ◔ʔ < Go

演習

  • ポインタを使わないメソッドでフィールドの値を変更するとどうなるか確認しよう

パッケージ

Go言語のソースはパッケージに属しています。 ソース先頭のpackageで所属するパッケージが定義されます。

エクスポート

構造体、関数、変数、定数の名前先頭を大文字にすることで外部パッケージからアクセスすることができるようになります。

package sample

// 先頭が大文字なので他のパッケージから利用できる
type MyStruct struct {
	Name string
}

// 先頭が大文字なので他のパッケージから利用できる
func MyFunc() {
	fmt.Println("MyFunc")
}
  • 小文字の場合、外部パッケージからアクセスできないことに注意
  • ただしアクセスするためのエクスポートされた関数があれば利用することは可能(シングルトンパターンなど)

インポート

外部パッケージを利用するにはインポートが必要です

// importキーワードによるパッケージのインポート
import "sample"

// パッケージ名. に続けて対象を指定する
myStruct := sample.MyStruct{Name: "a"}
sample.MyFunc()
  • GitHubなどで公開されているパッケージを利用するにはgithub.com/user/packageのような指定となります
  • 公開する自作パッケージは上記のディレクトリ構成でつくっておくと都合がよいです
  • 公開されているパッケージを利用するにはgo getコマンドでコードを取得しておきましょう

インターフェース

インターフェースは振る舞いを規定します

// Print()という関数を持つPrinterインターフェース型を宣言
type Printer interface {
	Print()
}

// MyStructはPrint()メソッドを持つ
// Printerインターフェースを実装するという宣言は必要ない
type MyStruct struct {
}

func (myStruct MyStruct) Print() {
	fmt.Println("MyStruct")
}

func main() {
	DoPrint(MyStruct{})
}

// DoPrintメソッドはPrinterインターフェースを引数にとる
func DoPrint(printer Printer) {
	printer.Print()
}
  • ひとつの役割(関数)しか持たないインターフェースはXXerと命名します

ʕ◔ϖ◔ʔ < Go

配列

Go言語の配列変数は配列全体を表しています(ポインタではありません)

// 配列長と型を指定する
var array1 [2]int

// 宣言と初期化
array2 := [2]int{1, 2}

// 初期化時は配列長を...で代替できる
array3 := [...]int{1, 2}

fmt.Printf("%s, %s, %s", array1, array2, array3)
  • 大きなサイズの配列を値渡しする場合は注意
  • 配列長をあとから変更することはできません

ʕ◔ϖ◔ʔ < Go

スライス

配列を参照する仕組み。 スライスの長さは動的で、スライス自体は参照型なので配列よりも扱いやすい

// スライス型
var slice []int
fmt.Println(slice)

// 配列からスライス
array1 := [3]int{1, 2, 3}
slice = array1[:]   // 配列全体をスライス
fmt.Println(slice)
slice = array1[1:2] // 配列の一部分をスライス
fmt.Println(slice)

// スライスとして宣言する
slice = []int{1, 2, 3}
fmt.Println(slice)

// スライスへの要素の追加
slice = append(slice, 4, 5)
fmt.Println(slice)

// スライスへのスライスの追加
slice2 := []int{6, 7}
slice = append(slice, slice2...)
fmt.Println(slice)
  • スライスへの要素の追加は可変長引数でいくつでも渡すことができます
  • スライスにスライスを追加するときは...が必要なので注意

ʕ◔ϖ◔ʔ < Go

演習

  • もとの配列長を超えて値を追加したらどうなるか考えてみよう (Hint)

マップ

キーと値の組み合わせの集合。

// マップ型
var m map[string]string

// makeによるマップの作成
m = make(map[string]string)

// マップとして宣言する
m = map[string]string{
        "first":  "a",
        "second": "b",
}

// 値へのアクセス
fmt.Println(m["first"])
  • スライスもマップも参照型だが、マップはもととなる要素(スライスでいう配列)にアクセスすることはできません
  • 参照型(スライス、マップ、チャネル)をつくるときはmakeを使います

ʕ◔ϖ◔ʔ < Go

range

rangeは要素の集合やチャネルに対する拡張forループを提供します

slice := []string{"a", "b", "c"}

// スライスのforループにrangeを利用する
for i, v := range slice {
	fmt.Println(i, v)
}

m := map[string]string{
	"first":  "a",
	"second": "b",
}

// マップのforループにrangeを利用する
for k, v := range m {
	fmt.Println(k, v)
}
  • チャネルに対するrangeはチャネルがクローズされると終了します

ʕ◔ϖ◔ʔ < Go

まとめ演習

  • path/filepathパッケージのWalkを使ってlsコマンドをつくってみよう
  • パッケージ、インターフェースを使って実装してみよう
  • go build コマンドを使ってバイナリを生成してみよう

もうちょっとGo

並列処理

処理を並行化するためにはGoroutineを使います。関数をGoroutine化するためには関数の呼出時にgoキーワードをつけます。
また、Goroutine間の値の共有にはchannelを使います。

このあたりについては、Go言語でgrep処理をつくりながら使い方を学んでもらえるようなスライドを公開しているので参考にしてみてください。

HTTPサーバ

Go言語は簡単にHTTPサーバを立てることができます

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", Hello) // '/'でアクセスされたときに呼ぶハンドラを定義
	err := http.ListenAndServe(":8080", nil) // 8080ポートでListen
	if err != nil {
		os.Exit(1)
	}
}

func Hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "<html><body>Hello!!</body></html>")
}

ルーティングやテンプレートエンジンなどを加えたフレームワークについては以下のようなものが続々公開されています。

構造体やインターフェース

Go言語の構造体はクラスではないので擬似的な継承しか行えません。
継承ベースの設計よりもインターフェースや移譲を使ったほうが無理なく実装ができるでしょう。

このあたりはGo言語での構造体実装パターンとしてブログに公開しているので参考にしてみてください。

ドキュメント

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment