3. 関数と型
@tenntenn
The Go gopher was designed by Renée French.
The gopher stickers was made by Takuya Ueda.
Licensed under the Creative Commons 3.0 Attributions license.
注意事項と免責事項
2
コメントと未完成部分について
3
質問について
4
上田拓也
Go ビギナーズ
Go Conference
@tenntenn
Google Developer Expert (Go)
一般社団法人 Gophers Japan 代表理事
バックエンドエンジニアとして日々Goを書いている。Google Developer Expert (Go)。一般社団法人Gophers Japan代表。Go Conference主催者。大学時代にGoに出会い、それ以来のめり込む。人類をGopherにしたいと考え、Goの普及に取り組んでいる。複数社でGoに関する技術アドバイザーをしている。マスコットのGopherの絵を描くのも好き。
作者
【PR】企業向け研修や技術アドバイザー
3.1. 型
8
変数と型
9
静的型付けの利点
10
組み込み型
11
整数 | int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, byte, rune |
浮動小数点数 | float32, float64 |
複素数 | complex64, complex128 |
文字列 | string |
真偽値 | bool |
インタフェース | error, any, comparable |
型変換(型のキャスト)
package main
func main() {
var f float64 = 10
var n int = int(f)
println(n)
}
// 変数vをT型にキャストする
T(v)
【TRY】組み込み型(数値)
13
package main
func main() {
var sum int
sum = 5 + 6 + 3
avg := sum / 3
if avg > 4.5 {
println("good")
}
}
【TRY】組み込み型(真偽値)
14
package main
func main() {
var a, b, c bool
if a && b || !c {
println("true")
} else {
println("false")
}
}
真理値表
15
a | b | c | a && b | !c | a && b || !c |
F | F | F | | | |
F | F | T | | | |
F | T | F | | | |
F | T | T | | | |
T | F | F | | | |
T | F | T | | | |
T | T | F | | | |
T | T | T | | | |
真理値表(解答)
16
a | b | c | a && b | !c | a && b || !c |
F | F | F | F | T | T |
F | F | T | F | F | F |
F | T | F | F | T | T |
F | T | T | F | F | F |
T | F | F | F | T | T |
T | F | T | F | F | F |
T | T | F | T | T | T |
T | T | T | T | F | T |
コンポジット型
17
型の種類 | 説明 |
構造体 | 型の異なるデータ型を集めたデータ型 |
配列 | 同じ型のデータを集めて並べたデータ型 |
スライス | 配列の一部を切り出したデータ型 |
マップ | キーと値をマッピングさせたデータ型 |
コンポジット型のゼロ値
型 | ゼロ値 |
構造体 | フィールドがすべてゼロ値 |
配列 | 要素がすべてゼロ値 |
スライス | nil |
マップ | nil |
型の表記と型宣言
プログラミング言語Go完全入門では
型自体と型宣言の解説を分けて行います
型リテラル
20
// int型のスライスの型リテラルを使った変数定義
var ns []int
// mapの型リテラルを使った変数定義
var m map[string]int
リテラルとは名前ではなく
そのものを書き下すものを指します
例:型リテラル、数値リテラルなど
スライス型を表す
[]int
要素の型を表す
構造体
21
var p struct {
name string
age int
}
型リテラル表記の
構造体
構造体タグ
var p struct {
name string `json:"name"`
age int `json:"age"`
}
タグは文字列リテラルであるため
"sampletag"のような値でも良い
構造体リテラル
23
// 構造体リテラルの例
p := struct {
name string
age int
}{
name: "Gopher",
age: 10,
}
構造体型 {
フィールドリスト...�}
型リテラル表記
フィールドのリスト
補足:型定義と構造体リテラル
// 型定義(後述)
type Person struct {
name string
age int
}
p := Person {
name: "Gopher",
age: 10,
}
構造体型 {
フィールドリスト...�}
型名
フィールドのリスト
文法で理解しよう
// 構造体型の変数
var p struct {
name string
age int
}
// int型の変数
var n int
// 変数定義の文法
var 変数名 型
構造はどちらも同じ
型リテラル
型名または型リテラル
フィールドの参照
26
p := struct {
name string
age int
}{ name: "Gopher", age: 10 }
// フィールドにアクセスする例
p.age++ // p.age = p.age + 1と同じ
println(p.name, p.age)
empty struct
var empty struct{} |
配列
28
// 型と要素数がセット
var ns [5]int
添字 | 0 | 1 | 2 | 3 | 4 |
値 | 10 | 20 | 30 | 40 | 50 |
配列の初期化
29
// ゼロ値で初期化
var ns1 [5]int
// 配列リテラルで初期化
var ns2 = [5]int{10, 20, 30, 40, 50}
// 要素数より少ない場合は先頭が埋められて残りはゼロ値
var ns3 = [5]int{10, 20}
// 要素数を値から推論
ns4 := [...]int{10, 20, 30, 40, 50}
// 5番目が50、10番目が100で他が0の要素数11の配列
ns5 := [...]int{5: 50, 10: 100}
配列の操作
30
ns := [...]int{10, 20, 30, 40, 50}
// 要素にアクセス
println(ns[3]) // 添字は変数でもよい
// 長さ
println(len(ns))
// スライス演算
fmt.Println(ns[1:3])
スライス
31
0 | 1 | 2 | 3 | 4 |
10 | 20 | 30 | 40 | 50 |
ポインタ | |
len | 2 |
cap | 4 |
スライス
配列
ns :=[...]int{10, 20, 30, 40, 50}
ns[1:3]
len = 2
cap = 4
スライスの初期化
32
// ゼロ値はnil
var ns1 []int
// 長さと容量を指定して初期化
// 各要素はゼロ値で初期化される
ns1 = make([]int, 3, 10)
// スライスリテラルで初期化
// 要素数は指定しなくてよい
// 自動で配列は作られる
var ns2 = []int{10, 20, 30, 40, 50}
// 5番目が50、10番目が100で他が0の要素数11のスライス
ns3 := []int{5: 50, 10: 100}
スライスと配列の関係
var array [10]int
ns := array[0:3] // または array[:3]
ns := make([]int, 3, 10)
var array2 = [...]int{10, 20, 30, 40, 50}
ms := array2[0:5] // または array[:]
ms := []int{10, 20, 30, 40, 50}
大体同じ処理
大体同じ処理
スライスの操作
34
ns := []int{10, 20, 30, 40, 50}
// 要素にアクセス
println(ns[3])
// 長さ
println(len(ns))
// 容量
println(cap(ns))
// 要素の追加
// 容量が足りない場合は背後の配列が再確保される
ns = append(ns, 60, 70)
println(len(ns), cap(ns)) // 長さと容量
appendの挙動
appendの挙動
a := []int{10, 20}
// [10 20] 2
fmt.Println(a, cap(a))
b := append(a, 30) // (1)
a[0] = 100 // (2)
// [10 20 30] 4
fmt.Println(b, cap(b))
c := append(b, 40) // (3)
b[1] = 200 // (4)
// [10 200 30 40] 4
fmt.Println(c, cap(c))
| 0 | 1 | | |
10番地 | 10 | 20 | | |
配列
ポインタ | 10番地 |
len | 2 |
cap | 2 |
ポインタ | 20番地 |
len | 3 |
cap | 4 |
ポインタ | 20番地 |
len | 4 |
cap | 4 |
| 0 | 1 | 2 | 3 |
20番地 | 10 | 20 | 30 | 0 |
a
b
c
(2) 100
(1)
(4) 200
(3) 40
スライスと配列の関係
100
101
102
103
ロッカー(配列)
開始番号: 100
いくつ分: 2
増やせる量:4
利用できる場所を
書いた紙(スライス)
配列をロッカー、スライスを自分が利用できるロッカーの情報と考えてみる
開始番号: 101
いくつ分: 3
増やせる量:2
別の紙で同じ場所のロッカーを見ている可能性もある
同じ情報が書かれた
別の紙(スライス)も存在しうる
スライスのコピー
開始番号: 10
いくつ分: 2
増やせる量:3
スライスa
開始番号: 10
いくつ分: 2
増やせる量:3
スライスb
a := []int{10, 20, 30}
// スライスはコピーされているが配列は同じ
b := a
コピー
| アドレス | | |
配列 | 10番地 | 10 | |
| | 20 | |
| | 30 | |
スライスa | 200番地 | 10番地 | ポインタ |
| | 2 | 長さ |
| | 3 | 容量 |
スライスb | 300番地 | 10番地 | ポインタ |
| | 2 | 長さ |
| | 3 | 容量 |
コピー
メモリマップによる表現
他の変数への代入は必ずコピーを伴う
コピー元と先は別の場所に保存された別物
同じ情報の書かれた別の紙のようなもの
配列・スライスへのスライス演算
ns := []int{10, 20, 30, 40, 50}
n, m := 2, 4
// n番目以降のスライスを取得する
fmt.Println(ns[n:]) // [30 40 50]
// 先頭からm-1番目までのスライスを取得する
fmt.Println(ns[:m]) // [10 20 30 40]
// capを指定する
ms := ns[:m:m]
fmt.Println(cap(ms)) // 4
// copy
dst := make([]int, len(ns))
copy(dst, ns[:2])
fmt.Println(dst) // [10 20 0 0 0]
// すべての要素をゼロ値にする
clear(ns)
fmt.Println(ns) // [0 0 0 0 0]
Slice Tricks
a = append(a[:i], a[j:]...)
a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]
slicesパッケージ
ns := []int{10, 20, 30, 40, 50} // 削除: [10 40 50] ns = slices.Delete(ns, 1, 3) fmt.Println(ns) // 挿入: [10 60 70 40 50] ns = slices.Insert(ns, 1, 60, 70) fmt.Println(ns) | // 要素があるか: true ok := slices.Contains(ns, 70) fmt.Println(ok) // ソート: [10 40 50 60 70] slices.Sort(ns) fmt.Println(ns) // 複製 ms := slices.Clone(ns) ns[0] = 100 fmt.Println(ms) // [10 40 50 60 70] |
【TRY】スライスの利用
42
package main
func main() {
n1 := 19
n2 := 86
n3 := 1
n4 := 12
sum := n1 + n2 + n3 + n4
println(sum)
}
マップ
43
キー | "a" | "b" | "c" | "d" | "e" |
値 | 10 | 20 | 30 | 40 | 50 |
// キーと値を指定する
var m map[string]int
マップの初期化
44
// ゼロ値はnil
var m map[string]int
// makeで初期化
m = make(map[string]int)
// 容量を指定できる
m = make(map[string]int, 10)
// リテラルで初期化
m := map[string]int{"x": 10, "y": 20}
// 空の場合(リテラル)
m := map[string]int{}
Uberのスタイルガイドでは
空の場合はリテラルよりmake関数を
使うほうが望ましいとされている
マップの操作
45
m := map[string]int{"x": 10, "y": 20}
// キーを指定してアクセス
println(m["x"])
// キーと値を紐づける
m["z"] = 30
// 存在を確認する
n, ok := m["z"]
println(n, ok)
// キーを指定して削除する
delete(m, "z")
// 削除されていることを確認
n, ok = m["z"] // ゼロ値とfalseを返す
println(n, ok)
// すべてのキーを削除する
clear(m)
fmt.Println(m) // map[]
マップの要素をfor文で取得する
m := map[string]int{"dog": 1, "cat":99}
// キーのみ
for k := range m { /* 略 */ }
// キーと値
for k, v := range m { /* 略 */ }
mapsパッケージ
m := map[string]int{"x": 10, "y": 20} // マップのキー(ランダム) for k := range maps.Keys(m) { fmt.Println(k) } // マップの値(ランダム) for v := range maps.Values(m) { fmt.Println(v) } | // マップのキー(ソート済) for k := range slices.Sorted(maps.Keys(m)) { fmt.Println(k) } // 複製 m2 := maps.Clone(m) delete(m, "x") fmt.Println(m2) |
マップとゼロ値
// var count map[string]intだとnilになるため初期化が必要
count := make(map[string]int)
for _, word := range strings.Split("cat cat dog cat fish dog cat", " ") {
count[word]++ // count[word] = count[word] + 1
}
// map[cat:4 dog:2 fish:1]
fmt.Println(count)
キーが存在しない場合はcount[word]が
0となりcount[word] = 0 + 1となる
コンポジット型を要素にする
struct {
A struct {
N int
}
}
要素も型リテラルで書かれているだけ
型宣言
エイリアス宣言(Alias Declarations)
51
type Applicant = http.Client
func main() {
fmt.Printf("%T", Applicant{}) // http.Client
}
byteはuint8、runeはint32、
anyはinterface{}の型エイリアス
type 型名 = 型
型定義(Type Definitions)
52
// 組み込み型を基にする
type MyInt int
// 他のパッケージの型を基にする
type MyWriter io.Writer
// 型リテラルを基にする
type Person struct {
Name string
}
type 型名 型
Underlying type
53
int
[]string
struct {N int}
X
type X struct {N int}
Y
type Y X
定義型の特徴
type MyInt int
var n int = 100
m := MyInt(n)
n = int(m)
// 10秒を表す(time.Duration型)
d := 10 * time.Second
type Duration int64
time.Duration型
【TRY】定義型の利用
55
3.2. 関数
56
関数
57
関数呼び出し
x := f(10, 1+1, y)
組み込み関数①
59
print/println | 表示を行う |
make | コンポジット型などの初期化 |
new | 指定した型のメモリの確保 |
len/cap | スライスなどの長さ/容量を返す |
copy | スライスのコピーを行う |
delete | マップから指定したキーのエントリを削除 |
complex | 複素数型を作成 |
imag/real | 複素数の虚部/実数部を取得 |
panic/recover | パニックを起こす/回復する |
組み込み関数②
60
close | チャネルを閉じる |
append | スライスへの要素の追加 |
clear | スライス/マップの要素を削除 |
min | 最小値を取得 |
max | 最大値を取得 |
関数宣言①
61
func add(x int, y int) int {
return x + y
}
関数名
引数
戻り値
戻り値を返す
関数宣言②
62
func swap(x, y int) (int, int) {
return y, x
}
複数の戻り値
カンマで区切って戻り値を返す
型をまとめる記述できる
多値の受け取り方
x, y := swap(10, 20)
第1戻り値
第2戻り値
x, _ := swap(10, 20)
_, y := swap(10, 20)
関数宣言③
64
func swap(x, y int) (x2, y2 int) {
y2, x2 = x, y
// 明示しない場合は戻り値の変数の値が変える
return
}
関数宣言④
65
func main() {
fmt.Println(sum(1, 2)) // 3
ns := []int{10, 20, 30}
fmt.Println(sum(ns...)) // 60
}
func sum(v ...int) int {
// vは[]int型
var sum int
for _, n := range v { sum += n }
return sum
}
値の入れ替え
x, y = y, x
無名関数
package main
func main() {
msg := "Hello, 世界"
func() {
println(msg)
}()
}
無名関数
無名関数を定義し
すぐに呼び出している
関数の外のmsgを参照できる
関数型
// string型を返す関数のスライス
fs := make([]func() string, 2)
fs[0] = func() string { return "hoge" }
fs[1] = func() string { return "fuga" }
for _, f := range fs {
fmt.Println(f())
}
クロージャと良くあるバグ
fs := make([]func(), 3)
for i := range fs {
fs[i] = func() { fmt.Println(i) }
}
for _, f := range fs { f() }
値のコピー
70
p := struct{age int;name string}{age:10,name: "Gopher"}
p2 := p // コピー
p2.age = 20
println(p.age, p.name)
println(p2.age, p2.name)
ポインタ
71
func f(xp *int) {
*xp = 100
}
func main() {
var x int
f(&x)
println(x)�}
intのポインタ型
*でポインタの指す先に値を入れる
&でポインタを取得する
x | 0 | 10番地 |
x | 100 | 10番地 |
xp | 10番地 | 20番地 |
f(&x)
内部でポインタを使っているデータ型
72
ns := []int{10, 20, 30}
ns2 := ns
ns[1] = 200
println(ns[0], ns[1], ns[2])
println(ns2[0], ns2[1], ns2[2])
【TRY】奇数偶数判定関数
73
package main�func main() {
for i := 1; i <= 100; i++ {
print(i)
if i%2 == 0 {
println("-偶数")
} else {
println("-奇数")
}
}
}
【TRY】複数戻り値の利用
74
package main
func main() {
n, m := swap(10, 20)
println(n, m)
}
【TRY】ポインタ
75
package main�func main() {
n, m := 10, 20
swap2(&n, &m)
println(n, m)
}
3.3. メソッド
76
メソッド
77
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
// 100をHex型として代入
var hex Hex = 100
// Stringメソッドを呼び出す
fmt.Println(hex.String())
レシーバ
78
type T int
func (t *T) f() { println("hi") }
func main() {
var v T
v.f() // (&v).f()と同じ意味
}
ポインタ型のメソッドリスト
79
func (t T) f() {}
func (t *T) g() {}
func main() {
(T{}).f() // T
(&T{}).f() // *T
(*&T{}).f() // T�
(T{}).g() // <- できない
(&T{}).g()
(*&T{}).g()
}
レシーバにできる型
80
【TRY】レシーバに変更を与える
81
package main
type MyInt int
func (n MyInt) Inc() { n++ }
func main() {
var n MyInt
println(n)
n.Inc()
println(n)
}
メソッド値
82
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
func main() {
var hex Hex = 100
var f func() string = hex.String
fmt.Println(f())
}
メソッド式
83
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("%x", int(h))
}
func main() {
var hex Hex = 100
var f func(Hex) string = Hex.String
fmt.Printf("%T\n%s\n", f, f(hex))
}
4. パッケージ 👉