1 of 84

3. 関数と型

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 of 84

注意事項と免責事項

  • 利用は個人の学習の範囲内でお願いします
    • この資料は個人の学習を目的とした利用に限ります
    • この資料を使った講義等を行う場合は事前に@tenntennに�許可を得てください
    • 生成AIを用いたサービスに学習させ、それを配布する行為を禁じます
  • 免責事項
    • この資料を元に発生した問題、この資料を参考にして作成した�ソフトウェア等に基づく問題について作成者は責任を負いません

2

3 of 84

コメントと未完成部分について

  • この資料にはコメントが残せます
    • コメントを誤って閉じる方が多いためコメント不可にしました
      • コメントをご希望の方はGopher道場 Slackにてお願いします
    • 分かりづらい点や間違いがあればコメントで指摘してください
    • この項目も追加してほしいとかでも構いません
    • 今後の資料づくりに反映させたいと思います
  • 未完成部分があります
    • この資料は絶賛作成中なので未完成な部分があります
    • タイトルだけのスライドは将来追加予定の項目です

3

4 of 84

質問について

  • Gophers Slackの#japanチャンネルでお願いします
    • Slackへの招待URL: https://invite.slack.golangbridge.org/
    • @tenntennまでメンションをください
    • ※質問への回答はすぐに行われるわけではないので予めご了承ください

4

5 of 84

上田拓也

Go ビギナーズ

Go Conference

Google Developer Expert (Go)

一般社団法人 Gophers Japan 代表理事

バックエンドエンジニアとして日々Goを書いている。Google Developer Expert (Go)。一般社団法人Gophers Japan代表。Go Conference主催者。大学時代にGoに出会い、それ以来のめり込む。人類をGopherにしたいと考え、Goの普及に取り組んでいる。複数社でGoに関する技術アドバイザーをしている。マスコットのGopherの絵を描くのも好き。

作者

6 of 84

【PR】企業向け研修や技術アドバイザー

  • Goに関する研修・講義
    • 初学者から中級者以上向けの講義を行えます
    • プログラミング言語Go完全入門をベースにカスタマイズ可能です
  • 技術アドバイザー
    • PRのレビュー
    • 週1回1時間程度のMeetやZoomでの相談
  • 依頼方法
    • フォームからお問い合わせください
    • 短期・長期のどちらでも契約が可能です
    • 講義・ハンズオン、技術相談などを組み合わせることも可能です
    • 実績等はhttps://tenntenn.dev/ja/job/をご覧ください

7 of 84

目次

  1. 関数
  2. メソッド

8 of 84

3.1. 型

8

9 of 84

変数と型

    • どういう種類の値かを表すもの
      • 整数、浮動小数点数、真偽値、文字列 など
      • 自分で作ることも可能(定義型)
    • 変数の型:どういう種類の値が変数に入るのかを表したもの
  • 動的型付け言語
    • プログラム実行時に型を検証する
    • 変数に型がなく、なんでも代入できることが多い
  • 静的型付け言語
    • コンパイル時に型を検証する
    • 変数に型がある、型が違うと代入できない
    • Goはこっち

9

10 of 84

静的型付けの利点

  • 実行前に型の不一致を検出できる
    • コンパイルが通れば型の不一致が起きない
    • 型の不一致によるバグは見つけづらい問題
  • 曖昧なものはエラーになる
    • 暗黙の型変換がない
      • 1 + "2" => "12"
    • 浮動小数点数と整数の演算など見つけづらいバグが起きにくい
  • 型推論がある
    • 明示的に型を書く必要がない場合が多い

10

11 of 84

組み込み型

  • 最初から使える型
    • 8や16はその型を表現するために必要なサイズ
      • int8は整数を表すために8ビット使用している
    • byteuint8runeint32、anyはinterface{}の型エイリアス
    • comparableは型制約のみに使えるインタフェース(ジェネリクス)

11

整数

int, int8, int16, int32, int64,

uint, uint8, uint16, uint32, uint64, uintptr, byte, rune

浮動小数点数

float32, float64

複素数

complex64, complex128

文字列

string

真偽値

bool

インタフェース

error, any, comparable

12 of 84

型変換(型のキャスト)

  • ある型から別の型に変換すること
    • 変換できない場合はコンパイルエラーになる
      • 例:int("hoge")

package main

func main() {

var f float64 = 10

var n int = int(f)

println(n)

}

// 変数vをT型にキャストする

T(v)

13 of 84

【TRY】組み込み型(数値)

  • 次のプログラムはコンパイルが通るでしょうか?
    • 動作確認を行ってみてください
    • コンパイルが通らない場合はなぜ通らないか考え修正してください

13

package main

func main() {

var sum int

sum = 5 + 6 + 3

avg := sum / 3

if avg > 4.5 {

println("good")

}

}

14 of 84

【TRY】組み込み型(真偽値)

  • 次のプログラムでtrueとなるケースを考えてください
    • trueと表示される場合のa,b,cの値にはどのようなパターンがあるか
    • 真理値表を作成し埋めて下さい

14

package main

func main() {

var a, b, c bool

if a && b || !c {

println("true")

} else {

println("false")

}

}

15 of 84

真理値表

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 of 84

真理値表(解答)

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 of 84

コンポジット型

  • 複数のデータ型が集まって1つのデータ型になっている

17

型の種類

説明

構造体

型の異なるデータ型を集めたデータ型

配列

同じ型のデータを集めて並べたデータ型

スライス

配列の一部を切り出したデータ型

マップ

キーと値をマッピングさせたデータ型

18 of 84

コンポジット型のゼロ値

  • データの表現方法によって違う
    • 構造体や配列は要素(フィールド)がすべてゼロ値の値
    • スライスやマップはmakeなどで初期化が必要なためnilとなる

ゼロ値

構造体

フィールドがすべてゼロ値

配列

要素がすべてゼロ値

スライス

nil

マップ

nil

19 of 84

型の表記と型宣言

  • 型の表記
    • 型をどのように表記(記述)するか
    • 名前または型リテラル(後述)で表記できる
    • 型を宣言する話とは異なる
  • 型宣言
    • 識別子(名前/型名)と型を紐づける
    • 紐づけ方法は2種類
      • 型定義(Type Definition)
      • エイリアス宣言(Type Alias)

プログラミング言語Go完全入門では

型自体と型宣言の解説を分けて行います

20 of 84

型リテラル

  • 型リテラルとは
    • 型の具体的な定義を書き下した型の表現方法
    • コンポジット型などを表現するために使う
    • 変数定義やユーザ定義型などで使用する

20

// int型のスライスの型リテラルを使った変数定義

var ns []int

// mapの型リテラルを使った変数定義

var m map[string]int

リテラルとは名前ではなく

そのものを書き下すものを指します

例:型リテラル、数値リテラルなど

スライス型を表す

[]int

要素の型を表す

21 of 84

構造体

  • 型の異なるデータ型の変数を集めたデータ構造
    • 各変数はフィールドと呼ばれる
      • 下の例では構造体pはフィールドnameageを持つ
    • フィールドの型は異なってもよい(同じ型も可)
    • フィールドの型には組み込み型以外も使える
      • コンポジット型やユーザ定義型も使える

21

var p struct {

name string

age int

}

型リテラル表記の

構造体

22 of 84

構造体タグ

  • フィールドに対する注釈
    • 文字列リテラルで指定する
    • encoding/jsonパッケージなどで利用する
      • JSONにした時の名前を指定できる
    • リフレクションまたは静的解析でしか取得できない
      • ラベル:"値"の形式は言語仕様で指定されていない

var p struct {

name string `json:"name"`

age int `json:"age"`

}

タグは文字列リテラルであるため

"sampletag"のような値でも良い

23 of 84

構造体リテラル

  • フィールドを指定して初期化(構造体リテラル)

23

// 構造体リテラルの例

p := struct {

name string

age int

}{

name: "Gopher",

age: 10,

}

構造体型 {

フィールドリスト...�}

型リテラル表記

フィールドのリスト

24 of 84

補足:型定義と構造体リテラル

  • 型定義を使った構造体リテラルが一般的

// 型定義(後述)

type Person struct {

name string

age int

}

p := Person {

name: "Gopher",

age: 10,

}

構造体型 {

フィールドリスト...�}

型名

フィールドのリスト

25 of 84

文法で理解しよう

  • プログラミング言語の文法は決まっている
    • 一見、難しい記述方法でも文法上ではそんなに変わらない

// 構造体型の変数

var p struct {

name string

age int

}

// int型の変数

var n int

// 変数定義の文法

var 変数名

構造はどちらも同じ

型リテラル

型名または型リテラル

26 of 84

フィールドの参照

  • .」(ドット)でアクセスする
    • フィールドの参照も代入も「.」を用いてアクセスする

26

p := struct {

name string

age int

}{ name: "Gopher", age: 10 }

// フィールドにアクセスする例

p.age++ // p.age = p.age + 1と同じ

println(p.name, p.age)

27 of 84

empty struct

  • サイズがゼロの型
    • 存在はしてほしいがデータ容量を使いたくない場合に使用
      • 例:マップのキーだけが重要な場合
        • map[string]struct{}
    • https://go.dev/ref/spec#Size_and_alignment_guarantees
    • 例:https://pkg.go.dev/structs
  • ポインタの扱いに注意
    • ポインタ同じ場合もあるが違う場合もある

var empty struct{}

28 of 84

配列

  • 同じ型のデータを集めて並べたデータ構造
    • 要素の型はすべて同じ
    • 要素数が違えば別の型
    • 要素数は変更できない
    • 型は型リテラルで記述することが多い

28

// 型と要素数がセット

var ns [5]int

添字 

0

1

2

3

4

値 

10

20

30

40

50

29 of 84

配列の初期化

  • 配列の初期化のいろいろ

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 of 84

配列の操作

30

ns := [...]int{10, 20, 30, 40, 50}

// 要素にアクセス

println(ns[3]) // 添字は変数でもよい

// 長さ

println(len(ns))

// スライス演算

fmt.Println(ns[1:3])

31 of 84

スライス

  • 配列の一部を切り出したデータ構造
    • 要素の型はすべて同じ
    • 要素数は型情報に含まない
    • 背後に配列が存在する

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 of 84

スライスの初期化

  • スライスの初期化のいろいろ

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}

33 of 84

スライスと配列の関係

  • スライスはベースとなる配列が存在している

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 of 84

スライスの操作

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)) // 長さと容量

35 of 84

appendの挙動

  • 容量が足りる場合
    • 新しい要素をコピーする
    • lenを更新する
  • 容量が足りない場合
    • 元のおよそ2倍の容量の配列を確保しなおす
      • 1024を超えた場合は、およそ1/2ずつ増える
    • 配列へのポインタを貼り直す
    • 元の配列から要素をコピーする
    • 新しい要素をコピーする
    • lencapを更新する

36 of 84

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

37 of 84

スライスと配列の関係

100

101

102

103

ロッカー(配列)

開始番号: 100

いくつ分: 2

増やせる量:4

利用できる場所を

書いた紙(スライス)

配列をロッカー、スライスを自分が利用できるロッカーの情報と考えてみる

開始番号: 101

いくつ分: 3

増やせる量:2

別の紙で同じ場所のロッカーを見ている可能性もある

同じ情報が書かれた

別の紙(スライス)も存在しうる

38 of 84

スライスのコピー

開始番号: 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

容量

コピー

メモリマップによる表現

他の変数への代入は必ずコピーを伴う

コピー元と先は別の場所に保存された別物

同じ情報の書かれた別の紙のようなもの

39 of 84

配列・スライスへのスライス演算

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]

40 of 84

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:])]

41 of 84

slicesパッケージ

  • スライスに関する便利なパッケージ
    • https://pkg.go.dev/slices
    • Go1.18で導入されたジェネリクスを使用している
    • Go1.21で標準ライブラリに入った

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]

42 of 84

【TRY】スライスの利用

  • 3つの変数しか使わないように修正してください
    • プログラムの動作はそのままにすること

42

package main

func main() {

n1 := 19

n2 := 86

n3 := 1

n4 := 12

sum := n1 + n2 + n3 + n4

println(sum)

}

43 of 84

マップ

  • キーと値をマッピングさせるデータ構造
    • キーと値の型を指定する
    • キーには「==」でゼロ値以外とも比較できる型しかNG
      • 比較不可能:関数、スライス、マップなど
    • ゴルーチンセーフではないため並行処理では注意
      • 代わりにsync.Map型を使用する

43

キー 

"a"

"b"

"c"

"d"

"e"

値 

10

20

30

40

50

// キーと値を指定する

var m map[string]int

44 of 84

マップの初期化

  • マップの初期化のいろいろ

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 of 84

マップの操作

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[]

46 of 84

マップの要素をfor文で取得する

  • for range文を使用する
    • 繰り返される順番はランダムになる

m := map[string]int{"dog": 1, "cat":99}

// キーのみ

for k := range m { /* 略 */ }

// キーと値

for k, v := range m { /* 略 */ }

47 of 84

mapsパッケージ

  • マップに関する便利なパッケージ
    • https://pkg.go.dev/maps
    • Go1.18で導入されたジェネリクスを使用している
    • Go1.21で標準ライブラリに入った

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)

48 of 84

マップとゼロ値

  • 存在しないキーを指定した場合の値はゼロ値
    • カウンタやフラグの初期値として使うと便利
    • わざわざ各キーの初期値を入れていく必要がない

// 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となる

49 of 84

コンポジット型を要素にする

  • コンポジット型を要素として持つコンポジット型
    • スライスの要素がスライスの場合(2次元スライス)
      • 例:[][]int
    • マップの値がスライスの場合
      • 例:map[string][]int
    • 構造体のフィールドの型が構造体
      • 例:

struct {

A struct {

N int

}

}

要素も型リテラルで書かれているだけ

50 of 84

型宣言

  • 宣言とは識別子(名前)とGoの各機能を紐づける機能
    • https://go.dev/ref/spec#Declarations_and_scope
    • 定数、型、型パラメータ、変数、関数、ラベル、パッケージ
    • consttypevarなどの予約語を付けて宣言を書く
  • 型宣言は型と識別子を紐づける

51 of 84

エイリアス宣言(Alias Declarations)

  • 型エイリアスを宣言する
    • 型に新しい識別子を紐づける
    • すでに型名を持つ型に対しても別名を紐づけ可能
    • 元の型と完全に同じ型になる

51

type Applicant = http.Client

func main() {

fmt.Printf("%T", Applicant{}) // http.Client

}

byteuint8runeint32

anyinterface{}の型エイリアス

type 型名 =

52 of 84

型定義(Type Definitions)

  • 新しい型を作り識別子を紐づける
    • Underlying Typeと操作が同じ独立した型(定義型)を作る
    • 基の型とはまったく別の型になる

52

// 組み込み型を基にする

type MyInt int

// 他のパッケージの型を基にする

type MyWriter io.Writer

// 型リテラルを基にする

type Person struct {

Name string

}

type 型名

53 of 84

Underlying type

  • すべての型が持つもの
    • int型やstring型などの組み込み型は自分自身
    • 型リテラルで記述された型も自分自身
    • 定義型はtype Y XXのUnderlying type
    • 型階層が作れないようになっている

53

int

[]string

struct {N int}

X

type X struct {N int}

Y

type Y X

54 of 84

定義型の特徴

  • 同じUnderlying typeを持つ型同士は型変換できる
  • 型なし定数から明示的な型変換は不要
    • デフォルトの型からユーザ定義型へ変換できる場合

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型

55 of 84

【TRY】定義型の利用

  • 次の仕様のデータ構造を考えてみてください
    • とあるゲームの得点を集計をするプログラム
    • ゲームの結果は0点から100点まで1点刻みで点数が付けられる
    • 集計は複数回のゲームの結果をもとにユーザごとに行う
    • どういうデータ構造で1回のゲーム結果を表現すべきか
    • 適切だと思う型定義をしてください

55

56 of 84

3.2. 関数

56

57 of 84

関数

  • 一連の処理をまとめたもの
    • 引数で受け取った値を基に処理を行い戻り値として結果を返す機能
      • 必ずしも引数や戻り値が無くてもよい
    • 引数:関数の入力となるものf(x)の場合x
    • 戻り値(返り値):関数の出力となるもの
  • 関数の種類
    • 組み込み関数
      • 言語の機能として組み込まれている関数
    • ユーザ定義関数
      • ユーザが定義した関数

57

58 of 84

関数呼び出し

  • 引数を指定して呼び出す
    • 引数は変数や式を指定もよい
    • 引数が複数ある場合はカンマで区切って指定する
    • 戻り値がある場合は変数に代入したり式中で使う

x := f(10, 1+1, y)

59 of 84

組み込み関数①

59

print/println

表示を行う

make

コンポジット型などの初期化

new

指定した型のメモリの確保

len/cap

スライスなどの長さ/容量を返す

copy

スライスのコピーを行う

delete

マップから指定したキーのエントリを削除

complex

複素数型を作成

imag/real

複素数の虚部/実数部を取得

panic/recover

パニックを起こす/回復する

60 of 84

組み込み関数②

60

close

チャネルを閉じる

append

スライスへの要素の追加

clear

スライス/マップの要素を削除

min

最小値を取得

max

最大値を取得

61 of 84

関数宣言①

  • 関数の宣言方法

61

func add(x int, y int) int {

return x + y

}

関数名

引数

戻り値

戻り値を返す

62 of 84

関数宣言②

  • 複数の戻り値を返す

62

func swap(x, y int) (int, int) {

return y, x

}

複数の戻り値

カンマで区切って戻り値を返す

型をまとめる記述できる

63 of 84

多値の受け取り方

  • カンマで区切って受け取ることができる
  • 省略したい場合は_(ブランク変数)を用いる

x, y := swap(10, 20)

第1戻り値

第2戻り値

x, _ := swap(10, 20)

_, y := swap(10, 20)

64 of 84

関数宣言③

  • 名前付き戻り値
    • 関数内では引数と同様に扱われる
    • 理由がない場合は利用しない
      • 戻り値の説明のため(同じ型の値を返す場合)
      • defer文で戻り値を変更する必要があるとき(後述)

64

func swap(x, y int) (x2, y2 int) {

y2, x2 = x, y

// 明示しない場合は戻り値の変数の値が変える

return

}

65 of 84

関数宣言④

  • 可変長引数
    • 最後の引数の数を任意の個数にできる(同じ型のみ)
    • 関数内ではスライスとして扱われる
    • スライスを...で展開する形で渡せる
      • 実際はスライスをそのまま渡しているのと同じなので注意

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

}

66 of 84

値の入れ替え

  • 一時変数なしで値を入れ替えることができる
    • 右辺もカンマ区切りで式を書くことができる

x, y = y, x

67 of 84

無名関数

  • 名前の無い関数のこと
    • クロージャとも呼ばれる

package main

func main() {

msg := "Hello, 世界"

func() {

println(msg)

}()

}

無名関数

無名関数を定義し

すぐに呼び出している

関数の外のmsgを参照できる

68 of 84

関数型

  • 関数はファーストクラスオブジェクト
    • 変数への代入
    • 引数に渡す
    • 戻り値で返す

// 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())

}

69 of 84

クロージャと良くあるバグ

  • 定義と実行のタイミングを気をつける
    • 関数外の変数(自由変数)を参照している場合
    • 実行のタイミングでは値が変わっている可能性がある
    • Go1.22からはバグが発生しない

fs := make([]func(), 3)

for i := range fs {

fs[i] = func() { fmt.Println(i) }

}

for _, f := range fs { f() }

70 of 84

値のコピー

  • 代入ではコピーが発生する
    • 代入元(コピー元)と同じ値がコピーされる
    • コピーのため、代入後(前)の変数に変更を加えても�代入前(後)の変数には影響を与えない
    • 関数の引数や戻り値でも同様のことが起きる

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 of 84

ポインタ

  • 変数の格納先を表す値
    • 値で渡される型の値に対して破壊的な操作を加える際に利用する
      • 破壊的な操作 = 関数を出てもその影響が残る

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 of 84

内部でポインタを使っているデータ型

  • 内部でポインタが用いられているデータ型
    • コンポジット型の一部
      • スライス
      • マップ
      • チャネル
    • これらの型はポインタを用いる必要がない場合が多い

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])

73 of 84

【TRY】奇数偶数判定関数

  • 奇数偶数判定関数を作成してください
    • そして、以下のプログラムの条件式の部分で使用して下さい

73

package main�func main() {

for i := 1; i <= 100; i++ {

print(i)

if i%2 == 0 {

println("-偶数")

} else {

println("-奇数")

}

}

}

74 of 84

【TRY】複数戻り値の利用

  • 値を入れ替えるswap関数を実装してください
    • 次のコードが正しく動作するように実装してください

74

package main

func main() {

n, m := swap(10, 20)

println(n, m)

}

75 of 84

【TRY】ポインタ

  • 値を入れ替えるswap2関数を実装してください
    • 次のコードが正しく動作するように実装してください

75

package main�func main() {

n, m := 10, 20

swap2(&n, &m)

println(n, m)

}

76 of 84

3.3. メソッド

76

77 of 84

メソッド

  • レシーバと紐付けられた関数
    • データとそれに対する操作を紐付けるために用いる
    • ドットでメソッドにアクセスする

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 of 84

レシーバ

  • メソッドに関連付けられた変数
    • メソッド呼び出し時には通常の引数と同じような扱いになる
      • コピーが発生する
      • レシーバがnilでも問題ない
    • ポインタを用いることでレシーバへの変更を呼び出し元に伝えることができる
      • レシーバがポインタの場合もドットでアクセスする

78

type T int

func (t *T) f() { println("hi") }

func main() {

var v T

v.f() // (&v).f()と同じ意味

}

79 of 84

ポインタ型のメソッドリスト

  • *T型はTのメソッドも自身のメソッドとして扱われる

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 of 84

レシーバにできる型

  • 定義型
  • ポインタ型
    • レシーバに変更を与えたい場合
  • 内部にポインタを持つ型
    • マップやスライスなどもレシーバにできる
    • appendしたい場合はスライスのポインタにする

80

81 of 84

【TRY】レシーバに変更を与える

  • 次のプログラムを正しく動作するようにしてください
    • Incメソッドは自身を1ずつ加算する
    • 今の実装だと正しく動かない
    • 動かない理由を考え、意図通り動くように修正してください

81

package main

type MyInt int

func (n MyInt) Inc() { n++ }

func main() {

var n MyInt

println(n)

n.Inc()

println(n)

}

82 of 84

メソッド値

  • メソッドも値として扱える
    • レシーバは束縛された状態

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 of 84

メソッド式

  • メソッドを表す式
    • レシーバを第1引数とした関数になる

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))

}

84 of 84