Go言語とはなにか? †
- 概要
- Google
- シンプルな言語仕様
- 高速
- 並行プログラミング
- 環境
- CentOS7 (Vagrant)
- sudo yum install golang
$ pwd
/home/vagrant/golang_lessons
$ sudo yum -y install golang
$ go version
go version go1.6.3 linux/amd64
はじめての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行コメントは「//」複数行コメントは「/* */」
- 文末に「;」は不要
- インデントはスペースではなくタブ推奨
変数を使ってみよう †
func main() {
var msg string
msg = "hello world"
fmt.Println(msg)
}
- 変数宣言と同時に値代入 ※ 型 (string) は省略可能
func main() {
var msg = "hello world"
fmt.Println(msg)
}
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文字目が大文字: 他のパッケージからもアクセス可
- 変数、定数、関数についても同じルール
基本データ型を使ってみよう †
- 文字列: string
- "hello"
- 初期値: 空文字 ※ nil では無い
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
定数を使ってみよう †
package main
import "fmt"
func main() {
const name = "yuji"
name = "shimojo"
fmt.Println(name)
}
$ go run hello.go
# command-line-arguments
./hello.go:7: cannot assign to name
- iota を使った定数宣言 ※ 自動で連番の値を付与
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
基本的な演算をしてみよう †
- 四則演算: + - * / %
- 文字列: +
- 論理値: AND(&&) OR(||) NOT(!)
func main() {
var x int
x = 10 % 3
fmt.Println(x)
}
$ go run hello.go
1
func main() {
var x int
x += 3 // x = x + 3
fmt.Println(x)
}
$ go run hello.go
3
- インクリメント ※ "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
ポインタを使ってみよう †
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
関数を使ってみよう †
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) を指定 ※ return 時に変数名指定が不要
func hi(name string) (msg string) {
msg = "hi!" + name
return
}
func main() {
fmt.Println(hi("yuji"))
}
$ go run hello.go
hi!yuji
- 返り値を2つ指定 ※ 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
配列を使ってみよう †
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
スライスを使ってみよう †
- 配列
- 関数に配列を渡す場合には値を丸ごと渡すためメモリ的に非効率
- 配列の要素数は固定になっていて動的に変化させることができない
- スライス
- 配列を便利に使えるようにしたデータ型
- 配列の一部または全部を指し示す参照型のデータ
- 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]
- スライスの値を変更 ※ 参照先の配列の値も変更されることに注意
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()を使おう †
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]
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 にコピー ※ 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
マップを使ってみよう †
- 添字にキーを使ってキーと値のペアで管理可能なデータ型
- 他の言語では ハッシュ や 連想配列 とも呼ばれている
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で条件分岐をしてみよう †
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!
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で条件分岐をしてみよう †
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
func main() {
score := 82
switch {
case score > 80:
fmt.Println("Great!")
default:
fmt.Println("so so ...")
}
}
$ go run hello.go
Great!
forでループ処理をしてみよう †
- 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
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
- 条件を省略 ※ 無限ループとなるため break 必須
func main() {
i := 0
for {
fmt.Println(i)
i++
if i == 3 { break }
}
}
$ go run hello.go
0
1
2
rangeを使ってみよう †
- 配列 / スライスやマップに対して要素分だけ何らかの処理を繰り返し行う場合に使える命令
- スライスの値を一つずつ取得して要素番号 (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
構造体を使ってみよう †
- 複数の値を意味のあるまとまりとして新しい型を定義することが可能
- フィールド: 構造体内で定義する各変数
- user 型の構造体定義 ※ 構造体 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}
- 構造体の初期化時に値を代入 ※ 返り値はポインタではなく値
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}
メソッドを使ってみよう †
- Go言語ではオブジェクト指向言語で提供されるクラスやメソッドは提供されていない
- Go言語のメソッドは構造体などのデータ型に紐付いた関数
- 関数をデータ型と紐づけるためにレシーバーと呼ばれるものを記述する
- メソッド (値渡し) ※ メソッドの引数として値がコピーされる
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
- メソッド (参照渡し) ※ 値渡しの場合だと 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
インターフェースを使ってみよう †
- メソッドの一覧を定義したデータ型
- 異なる構造体が共通のインターフェースを実装することで同一のスライスに格納して処理が可能
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!
空のインターフェース型を使おう †
- 空のインターフェースは実装すべきメソッドが無い
- これを活用することにより実際的に全てのデータ型を受け取ることができる関数が作成可能
- 処理中の実装には空のインターフェース型が何か (データ型が何か) を判別する必要あり
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)
}
}
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を使ってみよう †
- 並行処理を使わない場合 ※ 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!
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!
チャネルを使ってみよう †
- 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サーバーを作ってみよう †
- 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)
}