🐭

goの基本文法 No.4

2020/09/29に公開

2020-09-29執筆
A Tour of Goに沿ってGoの文法をまとめます。
これまでにJavaScriptやPythonなどのリッチ言語しか使ったことがなかった自分のためにまとめたものです。
なのでこれまでに上のようなリッチ言語を触ったことがあり、これからGoを触ってみようと考えている人の参考になればと思います。
文章の構成は、基本的に

  1. 概要
  2. コード
  3. コードの説明

の構成にしているつもりです。概要で簡単な説明をし、コードで例を示して、さらにコードの説明で細かい説明をするといった構成です。

配列

Array

配列は以下のようにして定義します。

var 変数名 [配列の個数]// 一般
変数名 := [配列の個数]{1,2, ...} // 短縮

具体例で見てみましょう。

// code:4-1
func main() {
    // 一般
	var a [2]string
	a[0] = "Hello" // ①
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)
    // 短縮
	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}
/*実行結果
Hello World
[Hello World]
[2 3 5 7 11 13]
*/

配列は配列の個数と型を定義します。短縮した記述では、その後{}の中に値を書きます。配列内の各要素へは①のようにキー(ここでは[0])を指定してアクセスします。他の言語と同様配列の最初の要素は[0]です。

宣言の時点で個数を指定しなければいけないのは扱い辛く感じますが後から可変長の配列の宣言のやり方も出てきます。

Slice

Slice配列の一部を取り出す機能です。

// code:4-2
func main() {
	number := [6]int{0, 1, 2, 3, 4, 5}
	
	a := number[1:4]
	fmt.Println(a)
}
/*実行結果
[1 2 3]
*/

上のコードではまず0~5までの6つの値を持った配列numberを宣言しています。number[0] = 0, number[1] = 1のようにキーと出力される値を統一しています。
number[1:4]でnumberから要素を取り出して変数aに代入しています。カギカッコの中のコロン:をまたいで左側が始まりを指定します。右側は指定した値の一つ前までを指定します。今回は[1:4]で指定していますが、1から4の前の3までが取り出されます。これは他の言語でも同様だと思います。

Sliceは参照渡し

Slice複製した配列は参照渡しになります。例えば複製した配列の値を書き換えると複製元の配列も書き換えられます。

// code:4-3
func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}
/*実行結果
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
*/

上のコードでは変数namesの一部をSliceコピーした配列a, bを作っています。その後bの要素を書き換えてそれぞれの配列を出力していますが、b以外のaとnamesも書き換えられています。このようにSlice参照渡しであり他の配列にも影響が出ます。

Sliceパターン

// code:4-4
func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[:]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}
/*実行結果
[2 3 5 7 11 13]
[2 3]
[3]
*/

Sliceの始まりを指定しなかったり、終わりを指定しなかったりしたパターンです。

LengthとCapacity

Sliceにはlengthとcapacityという概念があります。lengthは配列の長さです。今配列の中に入っている要素の数がlengthになります。capacityは容量です。その配列に格納出来る要素の数です。次のコードで確認しましょう。

// code:4-5
func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

    s = s[:0] // ①
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

	s = s[:4] // ②
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)

	s = s[2:] // ③
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
/*実行結果
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
*/

lengthとcapacityはそれぞれlen関数とcap関数で求めることが出来ます。lengthは要素の数なので左に出力されている配列と見比べてみるとわかりやすいと思います。capacityは最初の要素から数えて最後の要素までです。わかりにくいので詳しくみてみましょう。

  1. sliceで最初の要素[0]から0個の要素を取り出しています。しかし、s[0]~s[5]は値が入ってないだけで領域としては、確保されているのでcapacityは最初の要素s[0]から最後の要素までのcap=6になります。
  2. ここでは、s[:4]なので最初の要素から4つ目の要素までを取り出しています。s[4]~s[5]は値が入ってないだけで領域としては、確保されているのでこれもcapacityは最初の要素s[0]から最後の要素までのcap=6になります。
  3. これは、3番目の要素であるs[2]から取り出しています。なので最初の要素はs[2]であり、最初の要素から最後の要素までがcapacityになるのでcap=4になります。

nil slice

どのような型にもゼロ値が存在しました。配列の場合はどうなるでしょう。以下をご覧ください。

// code:4-6
func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil { // ①
		fmt.Println("nil!")
	}
}
/*実行結果 
[] 0 0
nil!
*/

上のコードでは初期値を設定せず、また途中で値の代入も行っていません。そのまま出力すると[]で出力されていますが、if文の条件ではnilと比べてtrueなら文字列を出力するようにしています。実行結果をみると文字列が出力されているので、空の配列snilは同一ということです。言い換えると空の配列のゼロ値はnilということになります。

nilは他の言語だとnullと表現されることが多いです。

make

sliceを作成するためにGoにはmake関数という組み込み関数(パッケージを読み込まなくても使える関数)が存在します。make関数で動的なサイズの配列を作成することが出来ます。

b := make([]int, n, m) // len(b)=n, cap(b)=m

上のようにmake関数に引数で、配列の型, length, capacityを与えるとその通りの配列を作成します。要素の値はゼロ値が適用されます。

// code:4-7
func main() {
	a := make([]int, 5)
	printSlice("a", a)

	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}
/*実行結果
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
*/

上のコードは実際にmake関数を使ってみたものとその実行結果です。これでイメージは掴めるかと思います。

配列の要素

配列の要素になる型は goの基本文法No.1 で紹介した型のみとは限りません。配列の要素として配列を取ったり、構造体を取ったり出来ます。

// code:4-8
// ①配列の要素が配列の例
func main() {
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}
/*実行結果
X _ X
O _ X
_ _ O
*/

// ②配列の要素が構造体の例
type Vertex struct {
	x float32
	y float32
}

func main() {
	vertexs := []Vertex{
		{0, 0}, {1, 2}, {3.14, 5},
	}
	fmt.Println(vertexs)
}
/*実行結果
[{0 0} {1 2} {3.14 5}]
*/

上のコードは、配列の要素として①配列や②構造体を取った例です。

配列の要素を増やす

配列のcapacityの数を超えて要素を追加するにはappend関数を使います。

append(配列, 追加する要素1, 追加する要素2, ...)

以下の例をみてみましょう。

// code:4-9
func main() {
	var s []int
	printSlice(s)

	s = append(s, 0)
	printSlice(s)

	s = append(s, 1) // ①
	printSlice(s)

	s = append(s, 2, 3, 4)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
/*実行結果
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]
*/

append関数は配列に新しい要素を追加した新しい配列を返します。なので①のように返り値を再代入しないと元の配列には反映されません。

// code:4-10
func main() {
	var s []int
	printSlice(s)
	s = append(s, 0)
	printSlice(s)
	a := append(s, 1)
	printSlice(a)
	b := append(s, 2, 3, 4)
	printSlice(b)
	
	a[0] = 1000
	fmt.Println("a[0]value change 1000")
	printSlice(a)
	printSlice(b)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
/*実行結果
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=4 cap=4 [0 2 3 4]
a[0]value change 1000
len=2 cap=2 [1000 1]
len=4 cap=4 [0 2 3 4]
len=1 cap=1 [0]
*/

ちなみにappend関数は参照渡しではなく新しい配列を渡すので、上のコードのように一部の要素を書き換えた結果が他の配列に影響することはありません。

range

配列の要素一つ一つを取り出して処理を行うにはfor-rangeステートメントを使います。

for キー, バリュー := range 配列 {
	// keyとvalueを使って処理を行う
}
// 使わないものは_"アンダーバー"にする
for _, バリュー := range 配列 {
	// valueだけを使って処理を行う
}

配列からはキーという要素の番号とバリューという要素の値の二つを取り出せます。配列の要素の最初から最後まで処理が繰り返されます。取り出した要素は繰り返し処理の中で必ず使う必要があります。使わないものは変数のところを_にしてください。

// code:4-11
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}
/*実行結果
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
*/

上のコードはpowという配列を定義し、for-rangeステートメントでそれぞれの要素を取り出し、出力しています。

Exercise: Slices

最後に該当するエクササイズの概要と私の解答を載せておきます。参考までに。
私の解答

Discussion