#contents ** Go言語とはなにか? [#d30d648d] ** Go言語とはなにか? [#z4caea8f] - 概要 -- Google -- シンプルな言語仕様 -- 高速 -- 並行プログラミング - 公式サイト -- https://golang.org/ - 前提知識 -- Unixコマンド -- C言語 - 環境 -- CentOS7 (Vagrant) -- sudo yum install golang - Goインストール $ pwd /home/vagrant/golang_lessons $ sudo yum -y install golang $ go version go version go1.6.3 linux/amd64 ** はじめてのGoプログラム [#p18d123b] ** はじめてのGoプログラム [#u15e3fc8] - hello.go package main import "fmt" func main() { fmt.Println("hello world") } - コンパイル $ go build hello.go $ ls hello hello.go - 実行 $ ./hello hello world - コンパイル&実行 $ go run hello.go hello world - 言語の特徴 -- 1行コメントは「//」複数行コメントは「/* */」 -- 文末に「;」は不要 -- インデントはスペースではなくタブ推奨 ** 変数を使ってみよう [#vd8a2dfc] ** 変数を使ってみよう [#ld66c6f8] - 基本形 func main() { var msg string msg = "hello world" fmt.Println(msg) } - 変数宣言と同時に値代入 &color(red){※}; 型 (string) は省略可能 func main() { var msg = "hello world" fmt.Println(msg) } - 変数宣言と同時に値代入 &color(red){※}; := を利用した記法 func main() { msg := "hello world" fmt.Println(msg) } - 実行結果 $ go run hello.go hello world - 複数変数を同時に宣言 func main() { // var a, b int // a, b = 10, 15 a, b := 10, 15 } - 型が異なる変数を同時に宣言 func main() { var ( c int d string ) c = 20 d = "hoge" } - 変数名の規則 -- 1文字目が小文字: そのパッケージ内からのみアクセス可 -- 1文字目が大文字: 他のパッケージからもアクセス可 -- 変数、定数、関数についても同じルール ** 基本データ型を使ってみよう [#n775ca6b] ** 基本データ型を使ってみよう [#nfef6c70] - 文字列: string -- "hello" -- 初期値: 空文字 "" &color(red){※}; nil では無い -- 初期値: 空文字 &color(red){※}; nil では無い - 整数: int -- 53 -- 初期値: 0 - 浮動小数: float64 -- 10.2 - 真偽値: bool -- false / true -- 初期値: false - nil - printf の使い方 package main import "fmt" func main() { a := 10 b := 12.3 c := "hoge" var d bool fmt.Printf("a:%d, b:%f, c:%s, d:%t\n", a, b, c, d) } - 実行結果 $ go run hello.go a:10, b:12.300000, c:hoge, d:false ** 定数を使ってみよう [#l78900cb] ** 定数を使ってみよう [#v40588d3] - const で宣言 - 値の変更不可 - hello.go package main import "fmt" func main() { const name = "taguchi" name = "fkoji" const name = "yuji" name = "shimojo" fmt.Println(name) } - 実行結果 $ go run hello.go # command-line-arguments ./hello.go:7: cannot assign to name - iota を使った定数宣言 &color(red){※}; 自動で連番の値を付与 package main import "fmt" func main() { const ( sun = iota // 0 mon // 1 tue // 2 ) fmt.Println(sun, mon, tue) } - 実行結果 $ go run hello.go 0 1 2 ** 基本的な演算をしてみよう [#me90869c] ** 基本的な演算をしてみよう [#v6694ecc] - 四則演算: + - * / % - 文字列: + - 論理値: AND(&&) OR(||) NOT(!) - 余りの計算 func main() { var x int x = 10 % 3 fmt.Println(x) } - 実行結果 $ go run hello.go 1 - x = x + 3 の代入 func main() { var x int x += 3 // x = x + 3 fmt.Println(x) } - 実行結果 $ go run hello.go 3 - インクリメント &color(red){※}; "x = x++" や "++x" の記法はGoでは使えない func main() { var x int x++ fmt.Println(x) } - 実行結果 $ go run hello.go 1 - 文字列の連結 func main() { var s string s = "hello " + "world" fmt.Println(s) } - 実行結果 $ go run hello.go hello world - 論理値の演算 func main() { a := true b := false fmt.Println(a && b) fmt.Println(a || b) fmt.Println(!a) } - 実行結果 $ go run hello.go false true false ** ポインタを使ってみよう [#fe70ae83] ** ポインタを使ってみよう [#ie5aa5f1] - ポインタ -- メモリ上のアドレスを指し示す変数 -- 演算はできない - hello.go package main import "fmt" func main() { a := 5 var pa *int pa = &a // &a = aのアドレス // paの領域にあるデータの値 = *pa fmt.Println(pa) fmt.Println(*pa) } - 実行結果 $ go run hello.go 0xc82000a2a0 5 ** 関数を使ってみよう [#aa05ab6e] ** 関数を使ってみよう [#r985b6fc] - 基本形 (引数なし) func hi() { fmt.Println("hi!") } func main() { hi() } - 実行結果 $ go run hello.go hi! - 基本形 (引数あり) func hi(name string) { fmt.Println("hi!" + name) } func main() { hi("yuji") } - 実行結果 $ go run hello.go hi!yuji - 返り値を指定 func hi(name string) string { // 返り値の型を指定 msg := "hi!" + name return msg } func main() { fmt.Println(hi("yuji")) } - 実行結果 $ go run hello.go hi!yuji - 返り値に変数名 (msg) を指定 &color(red){※}; return 時に変数名指定が不要 func hi(name string) (msg string) { msg = "hi!" + name return } func main() { fmt.Println(hi("yuji")) } - 実行結果 $ go run hello.go hi!yuji - 返り値を2つ指定 &color(red){※}; Go言語の特徴として返り値を2つ指定することが可能 func swap(a, b int) (int, int) { return b, a } func main() { fmt.Println(swap(5, 2)) } - 関数を変数に代入 func main() { f := func(a, b int) (int, int) { // 関数名は省略可 return b, a } fmt.Println(f(2, 3)) } - 実行結果 $ go run hello.go 3 2 - 即時関数 func main() { func(msg string) { fmt.Println(msg) }("yuji") } - 実行結果 $ go run hello.go yuji ** 配列を使ってみよう [#c7615a8d] ** 配列を使ってみよう [#u958a099] - 基本形 func main() { var a [5]int // a[0] - a[4] a[2] = 3 a[4] = 10 fmt.Println(a) fmt.Println(a[2]) } - 実行結果 $ go run hello.go [0 0 3 0 10] 3 - 配列の宣言と値の代入を同時に実施 func main() { // b := [3]int{1, 3, 5} b := [...]int{1, 3, 5} // 配列の個数は自明のため省略 (...) 可 fmt.Println(b) fmt.Println(len(b)) // 配列の要素の個数を取得 } - 実行結果 $ go run hello.go [1 3 5] 3 ** スライスを使ってみよう [#n8d5f661] ** スライスを使ってみよう [#e8115998] - 配列 -- 関数に配列を渡す場合には値を丸ごと渡すためメモリ的に非効率 -- 配列の要素数は固定になっていて動的に変化させることができない - スライス -- 配列を便利に使えるようにしたデータ型 -- 配列の一部または全部を指し示す参照型のデータ -- Go言語においては配列よりもスライスがよく使われる - 配列の一部をスライスで参照 func main() { a := [5]int{2, 10, 8, 15, 4} s := a[2:4] // [8, 15] ... 配列 a の添字は省略可 [:] [:4] [2:] 等 fmt.Println(a) fmt.Println(s) } - 実行結果 $ go run hello.go [2 10 8 15 4] [8 15] - スライスの値を変更 &color(red){※}; 参照先の配列の値も変更されることに注意 func main() { a := [5]int{2, 10, 8, 15, 4} s := a[2:4] // [8, 15] s[1] = 12 fmt.Println(a) fmt.Println(s) } - 実行結果 $ go run hello.go [2 10 8 12 4] [8 12] - len (要素数), cap (スライスの最大容量: スライスの最初の位置から切り出せる最大個数) func main() { a := [5]int{2, 10, 8, 15, 4} s := a[2:4] // [8, 15] s[1] = 12 fmt.Println(len(s)) fmt.Println(cap(s)) } - 実行結果 $ go run hello.go 2 3 ** make()、append()、copy()を使おう [#qe032704] ** make()、append()、copy()を使おう [#w048b810] - make を使ってスライスを宣言 (初期値は0) func main() { s := make([]int, 3) // [0 0 0] fmt.Println(s) } - make を使ってスライスを宣言 (初期値を指定) func main() { s := []int{1, 3, 5} fmt.Println(s) } - 実行結果 $ go run hello.go [1 3 5] - append を使ってスライスの末尾に要素を追加 func main() { s := []int{1, 3, 5} // append s = append(s, 8, 2, 10) fmt.Println(s) } - 実行結果 $ go run hello.go [1 3 5 8 2 10] - copy を使ってスライス s をスライス t にコピー &color(red){※}; copy() の返り値はコピーした要素の数 func main() { s := []int{1, 3, 5} // append s = append(s, 8, 2, 10) // copy t := make([]int, len(s)) n := copy(t, s) fmt.Println(s) fmt.Println(t) fmt.Println(n) } - 実行結果 $ go run hello.go [1 3 5 8 2 10] [1 3 5 8 2 10] 6 ** マップを使ってみよう [#m6f26d89] ** マップを使ってみよう [#u24de239] - 添字にキーを使ってキーと値のペアで管理可能なデータ型 - 他の言語では ハッシュ や 連想配列 とも呼ばれている - 基本形 func main() { m := make(map[string]int) // key: string型, value: int型 m["yuji"] = 200 m["shimojo"] = 300 fmt.Println(m) } - 実行結果 $ go run hello.go map[yuji:200 shimojo:300] - マップの宣言と key / value の代入を同時に実施 func main() { m := map[string]int{"yuji":100, "shimojo":200} fmt.Println(m) } - 実行結果 $ go run hello.go map[shimojo:200 yuji:100] - len (key/valueペアの要素数), delete (ペアの削除) func main() { m := map[string]int{"yuji":100, "shimojo":200} fmt.Println(m) fmt.Println(len(m)) delete(m, "yuji") fmt.Println(m) } - 実行結果 $ go run hello.go map[yuji:100 shimojo:200] 2 map[shimojo:200] - マップに特定のキーが存在するか確認 func main() { m := map[string]int{"yuji":100, "shimojo":200} v, ok := m["shimojo"] fmt.Println(v) fmt.Println(ok) } - 実行結果 $ go run hello.go 200 true ** ifで条件分岐をしてみよう [#y54f0b66] ** ifで条件分岐をしてみよう [#jf0f6a42] - 基本形 func main() { score := 83 if score > 80 { fmt.Println("Great!") } else if score > 60 { fmt.Println("Good!") } else { fmt.Println("so so...") } } - 実行結果 $ go run hello.go Great! - if 文の中で変数を定義 func main() { if score := 43; score > 80 { fmt.Println("Great!") } else if score > 60 { fmt.Println("Good!") } else { fmt.Println("so so...") } // fmt.Println(score) // score は if 文の中のスコープのため上記は undefined エラーとなる } - 実行結果 $ go run hello.go so so... - if 文 の条件で利用可能な演算子 -- 比較演算子: > >= < <= == != -- 論理演算子: && || ! ** switchで条件分岐をしてみよう [#pc429ac3] ** switchで条件分岐をしてみよう [#ke0f0c15] - 基本形 func main() { signal := "blue" switch signal { case "red": fmt.Println("Stop") case "yellow": fmt.Println("Caution") case "green", "blue": fmt.Println("Go") default: fmt.Println("wrong signal") } } - 実行結果 $ go run hello.go Go - if else 文を switch 文で置き換え func main() { score := 82 switch { case score > 80: fmt.Println("Great!") default: fmt.Println("so so ...") } } - 実行結果 $ go run hello.go Great! ** forでループ処理をしてみよう [#e58240f0] ** forでループ処理をしてみよう [#n543ff36] - Go言語では繰り返し処理は for 文のみ (while 文は存在しない) - 基本形 func main() { for i := 0; i < 10; i++ { // if i == 3 { break } // 0, 1, 2 if i == 3 { continue } // 0, 1, 4, 5, 6, 7, 8, 9 fmt.Println(i) } } - 実行結果 $ go run hello.go 0 1 2 4 5 6 7 8 9 - while 文風な記法 func main() { i := 0 for i < 10 { fmt.Println(i) i++ } } - 実行結果 $ go run hello.go 0 1 2 3 4 5 6 7 8 9 - 条件を省略 &color(red){※}; 無限ループとなるため break 必須 func main() { i := 0 for { fmt.Println(i) i++ if i == 3 { break } } } - 実行結果 $ go run hello.go 0 1 2 ** rangeを使ってみよう [#jb8bbb93] - 配列 / スライスやマップに対して要素分だけ何らかの処理を繰り返し行う場合に使える命令 - スライスの値を一つずつ取得して要素番号 (index) と値 (value) を表示 func main() { s := []int{2, 3, 8} for i, v := range s { fmt.Println(i, v) } } - 実行結果 $ go run hello.go 0 2 1 3 2 8 - 要素番号 (index) の代わりにブランク修飾子を使って値 (value) のみ表示 func main() { s := []int{2, 3, 8} for _, v := range s { fmt.Println(v) } } - 実行結果 $ go run hello.go 2 3 8 - マップに対しての繰り返し処理 func main() { m := map[string]int{"yuji":200, "shimojo":300} for k, v := range m { fmt.Println(k, v) } } - 実行結果 $ go run hello.go yuji 200 shimojo 300 ** 構造体を使ってみよう [#u2b6cbbe] - 複数の値を意味のあるまとまりとして新しい型を定義することが可能 - フィールド: 構造体内で定義する各変数 - user 型の構造体定義 &color(red){※}; 構造体 u の返り値は空文字と0で構成される構造体のポインタ (アドレス) type user struct { name string score int } func main() { u := new(user) fmt.Println(u) } - 実行結果 $ go run hello.go &{ 0} - フィールドに値を代入 type user struct { name string score int } func main() { u := new(user) // (*u).name = "yuji" u.name = "yuji" u.score = 20 fmt.Println(u) } - 実行結果 $ go run hello.go &{yuji 20} - 構造体の初期化時に値を代入 &color(red){※}; 返り値はポインタではなく値 type user struct { name string score int } func main() { // u := user{"yuji", 50} u := user{name:"yuji", score:50} fmt.Println(u) } - 実行結果 $ go run hello.go {yuji 50} ** メソッドを使ってみよう [#l11b9595] - Go言語ではオブジェクト指向言語で提供されるクラスやメソッドは提供されていない - Go言語のメソッドは構造体などのデータ型に紐付いた関数 - 関数をデータ型と紐づけるためにレシーバーと呼ばれるものを記述する - メソッド (値渡し) &color(red){※}; メソッドの引数として値がコピーされる type user struct { name string score int } func (u user) show() { fmt.Printf("name:%s, score:%d\n", u.name, u.score) } func main() { u := user{name:"yuji", score:50} u.show() } - 実行結果 $ go run hello.go name:yuji, score:50 - メソッド (参照渡し) &color(red){※}; 値渡しの場合だと u.show() の score が加算されない type user struct { name string score int } func (u user) show() { fmt.Printf("name:%s, score:%d\n", u.name, u.score) } func (u *user) hit() { u.score++ } func main() { u := user{name:"yuji", score:50} u.hit() u.show() } - 実行結果 $ go run hello.go name:yuji, score:51 ** インターフェースを使ってみよう [#k49ab556] - メソッドの一覧を定義したデータ型 - 異なる構造体が共通のインターフェースを実装することで同一のスライスに格納して処理が可能 package main import "fmt" type greeter interface { greet() } type japanese struct {} type american struct {} func (j japanese) greet() { fmt.Println("Konnnichiwa!") } func (a american) greet() { fmt.Println("Hello!") } func main() { greeters := []greeter{japanese{}, american{}} for _, greeter := range greeters { greeter.greet() } } - 実行結果 $ go run hello.go Konnnichiwa! Hello! ** 空のインターフェース型を使おう [#b2a57e42] - 空のインターフェースは実装すべきメソッドが無い - これを活用することにより実際的に全てのデータ型を受け取ることができる関数が作成可能 - 処理中の実装には空のインターフェース型が何か (データ型が何か) を判別する必要あり - 型アサーションを使ったデータ型判別 func show(t interface{}) { // 型アサーション _, ok := t.(japanese) if ok { fmt.Println("i am japanese") } else { fmt.Println("i am not japanese") } } type greeter interface { greet() } type japanese struct {} type american struct {} func (j japanese) greet() { fmt.Println("Konnnichiwa!") } func (a american) greet() { fmt.Println("Hello!") } func main() { greeters := []greeter{japanese{}, american{}} for _, greeter := range greeters { greeter.greet() show(greeter) } } - 型Switchを使ったデータ型判別 func show(t interface{}) { // 型Switch switch t.(type) { case japanese: fmt.Println("i am japanese") default: fmt.Println("i am not japanese") } } type greeter interface { greet() } type japanese struct {} type american struct {} func (j japanese) greet() { fmt.Println("Konnnichiwa!") } func (a american) greet() { fmt.Println("Hello!") } func main() { greeters := []greeter{japanese{}, american{}} for _, greeter := range greeters { greeter.greet() show(greeter) } } - 実行結果 $ go run hello.go Konnnichiwa! i am japanese Hello! i am not japanese ** goroutineを使ってみよう [#xff5aec0] - 並行処理を簡易的に実装可能 - 並行処理を使わない場合 &color(red){※}; task1 の完了を待って task2 が実行される package main import ( "fmt" "time" ) func task1() { time.Sleep(time.Second * 2) fmt.Println("task1 finished!") } func task2() { fmt.Println("task2 finished!") } func main() { task1() task2() } - 実行結果 $ go run hello.go task1 finished! task2 finished! - 並行処理 (goroutine) を使った場合 package main import ( "fmt" "time" ) func task1() { time.Sleep(time.Second * 2) fmt.Println("task1 finished!") } func task2() { fmt.Println("task2 finished!") } func main() { go task1() go task2() time.Sleep(time.Second * 3) // goroutine が終わる前に main 関数が終了しないように待ち時間を設ける } - 実行結果 $ go run hello.go task2 finished! task1 finished! ** チャネルを使ってみよう [#pfe3149f] - goroutine のデータの受け渡しをするパイプ - 参照型のデータ型 (chan 型) package main import ( "fmt" "time" ) func task1(result chan string) { time.Sleep(time.Second * 2) result<- "task1 result" } func task2() { fmt.Println("task2 finished!") } func main() { result := make(chan string) go task1(result) go task2() fmt.Println(<-result) time.Sleep(time.Second * 3) } - 実行結果 $ go run hello.go task2 finished! task1 result ** Webサーバーを作ってみよう [#t515df73] - net/http パッケージを利用した web サーバー構築 package main import ( "fmt" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hi %s!", r.URL.Path[1:]) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } - 実行後 8080 番で Listen するのでブラウザ経由でアクセス - http://192.168.33.10:8080/ へアクセスすると "Hi" と表示 - http://192.168.33.10:8080/yuji/ へアクセスすると "Hi yuji!" と表示 - &color(red){※}; 事前に firewalld を停止しておくこと