😀

『初めてのGo言語 第2版』読書&学習ログ(第3章)

に公開

はじめに

第1〜2章に続き、第3章で学んだポイントを要点+検証コードでまとめます。
前回の記事はこちら👇

https://qiita.com/nemdull/items/4a6a9df723fefad6c0bc

配列と型:サイズが違うと別型

  • 配列は長さを含めて型が決まる。[3]int[4]int は別型で、代入も代入互換も無い。
package main

func main() {
	var a [3]int
	var b [4]int
	// ❌ cannot use b (type [4]int) as type [3]int
}

スライスの初期化・比較:nil と空、比較は不可

宣言のみ(未初期化)のスライスは nil。

一方、リテラル []T{} や make([]T, 0) は空スライス(len=0)だが nil ではない。

スライス同士の比較はできない(== は nil との比較のみOK)。

package main

import "fmt"

func main() {
	var s []int          // nil
	t := []int{}         // 空だが非nil
	u := make([]int, 0)  // 空だが非nil

	fmt.Println(s == nil) // true
	fmt.Println(t == nil) // false
	fmt.Println(u == nil) // false

	// fmt.Println(s == t)  // ❌ slice can only be compared to nil
}

関数呼び出しは値渡し

Goは値渡し。スライスは「ヘッダ(ポインタ等)のコピーなので、

要素の変更は元にも影響(同じ配列を指すため)。

append で再割当が起きると別配列に移り、以降は元に影響しない

package main

import "fmt"

func add1(n int) { n++ }
func touchElem(s []int) { s[0] = 99 }          // 要素変更 → 共有配列が書き換わる

func main() {
	i := 10
	add1(i)
	fmt.Println(i) // 10(値渡し)

	a := []int{1, 2, 3}
	touchElem(a)
	fmt.Println(a) // [99 2 3](値渡し)
}

make で容量(cap) を先取り

スライスの将来の append を見越して cap を大きめに。

package main

import "fmt"

func main() {
	s := make([]int, 0, 5) // len=0 cap=5
	fmt.Println(len(s), cap(s)) // 0 5
	s = append(s, 1,2,3,4,5)
	fmt.Println(len(s), cap(s)) // 5 5(ちょうど)
}

サブスライス式とフルスライス式

package main

import "fmt"

func main() {
	x := make([]string, 0, 5)
	x = append(x, "a","b","c","d") // len=4 cap=5

	y := x[:2]    // サブスライス:len=2 cap=5(共有・危険)
	z := x[:2:2]  // フルスライス:len=2 cap=2(cap制限・安全)

	y = append(y, "Y","Y") // 共有配列に追記→xが汚染され得る
	z = append(z, "Z")     // cap不足→新配列に退避→xは無傷

	fmt.Println("x:", x) // x: [a b Y Y]
	fmt.Println("y:", y) // y: [a b Y Y]
	fmt.Println("z:", z) // z: [a b Z]
}

配列⇄スライス:共有と非共有

配列→スライス:s := arr[:] は同じ配列を共有(書き換えが相互に見える)。

スライス→配列:スライスの要素を変更しても配列には影響しない。

package main

import "fmt"

func main() {
    // 配列からスライスへ
	arr := [4]int{1,2,3,4}
	s := arr[:]     // 共有
	s[0] = 99
	fmt.Println(arr) // [99 2 3 4]
}

copy は共有しない(ディープコピーの一種)

package main

import "fmt"

func main() {
	src := []int{1,2,3}
	dst := make([]int, len(src))
	copy(dst, src)
	dst[0] = 999
	fmt.Println(src) // [1 2 3]
	fmt.Println(dst) // [999 2 3]
}

文字列・rune・UTF-8

文字列はUTF-8のバイト列(不変)。len はバイト数。

rune は int32 の別名。

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	s := "こんにちは"           // 1文字=3バイト、合計15バイト
	fmt.Println(len(s))           // 15(バイト数)
	fmt.Println(utf8.RuneCountInString(s)) // 5(rune数)

	// インデックスはバイト単位
	fmt.Printf("%x\n", s[0])      // e3(先頭バイト)

	// range はrune単位
	for i, r := range s {
		fmt.Printf("i=%d r=%c\n", i, r)
	} // こ→ん→に→ち→は
    
    // 文字列⇄runeスライス
	rs := []rune(s)
	rs[0] = 'さ'
	fmt.Println(string(rs)) // 「さんにちは」
}

文字列のインデックス/スライスは1バイト文字のみで扱うようにする。
日本語などの多バイトでは途中で切ると壊れたUTF-8になる。

推奨:日本語など多バイトを扱うときは []rune に変換してからインデックス/スライスする。

「カンマOK」イディオム

マップ参照:キーの存在確認

package main

import "fmt"

func main() {
	// 在庫数を持つシンプルなマップ
	stock := map[string]int{
		"apple":  3,
		"banana": 0, // 在庫0(キーはある)
	}

	// あるキー
	v, ok := stock["apple"]
	fmt.Println("apple:", v, ok) // 3 true

	// ないキー(ゼロ値が返るが、okがfalseで判別できる)
	v2, ok2 := stock["orange"]
	fmt.Println("orange:", v2, ok2) // 0 false

	// 在庫0と“そもそも存在しない”を区別できるのがポイント
	v3, ok3 := stock["banana"]
	fmt.Println("banana:", v3, ok3) // 0 true
}

集合(Set) をマップで表現

存在だけを表したいので、値型はstruct{}(ゼロサイズ)か bool が定番。

package main

import "fmt"

func main() {
	// 値にboolを使ったSet(存在=true)
	set := map[string]bool{}

	// 追加
	set["go"] = true
	set["rust"] = true

	// 存在確認(キーがなければ false が返る)
	fmt.Println(set["go"])   // true
	fmt.Println(set["java"]) // false

	// 削除
	delete(set, "go")
	fmt.Println(set["go"]) // false
}

構造体(struct)と合成(埋め込み)

Go にはクラスや継承はない。代わりに 構造体の埋め込みやインターフェース、委譲でする。

まとめ(第3章)

  • 配列は長さを含めて型。サイズ違いは別型。

  • スライスはnilと空を区別、比較はnilとのみ。

  • Goの関数は値渡しだが、スライスはヘッダのコピーなので要素変更は共有配列に影響。

  • make で cap 先取り、フルスライスで cap 制限すると安全。

  • 配列→スライスは共有、copy は非共有。

  • 文字列はUTF-8の不変バイト列。インデックス/スライスは1バイト文字のみで、多バイトは []rune/utf8 を使う。

  • カンマOK(map)で存在する値とゼロ値を区別する。

  • クラス/継承はなく、構造体+埋め込み+インターフェースで設計する。

GitHubで編集を提案

Discussion