LoginSignup
0
0

More than 1 year has passed since last update.

【A Tour of Go】基本編③ 〜構造体、配列、スライス、マップ〜

Last updated at Posted at 2022-05-31

公式の A Tour of Go ではじめて Go 言語を触ってみました。
以下、構造体、配列、スライス、マップについて学んだことのメモ。

前回の記事はこちら

ポインタ

  • ポインタは値のメモリアドレスを指す
  • 変数Tのポインタは*T型で、ゼロ値はnil
var p *int
  • &演算子は、そのオペランドへのポインタを引き出す
i := 42
p = &i
  • *演算子は、ポインタの指す先の変数を示す(間接参照
fmt.Println(*p)
*p = 21 // ポインタ p を介して i へ値を代入する

構造体

構造体とは

  • 構造体(struct)は、フィールドの集まり
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2}) // {1 2}
}

フィールドへのアクセス

  • 構造体のフィールドは、ドット.を用いてアクセスする
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X) // 4
}
  • 構造体のフィールドは、構造体のポインタを通してアクセスすることができる
    • フィールドXを持つ struct のポインタp
    • フィールドXを間接参照するには(*p).X
    • しかしこの表記は面倒なので、Goでは代わりにp.Xと書ける
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v  // ポインタ
	p.X = 1e9 // Xへの間接参照 (*p).X
	fmt.Println(v) // {1000000000 2}
}

構造体リテラル

  • 構造体を初期化する方法
    • フィールドの初期値を{}内に列挙
    • {Name: }構文を使う
package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2} // has type Vertex
	v2 = Vertex{X: 1} // Y:0 is implicit
	v3 = Vertex{}     // X:0 and Y:0
	p = &Vertex{1, 2} // has type *Vertex

func main() {
	fmt.Println(v1, p, v2, v3)
}

配列

配列とは

  • [n]T型は、型Tn個の変数の配列を表す
  • 固定長
package main

import "fmt"

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

スライス

スライスとは

  • []Tは型Tのスライスを表す
  • 可変長
  • より柔軟な配列と見なすことができる
  • 配列からスライスを抜き出す方法
    • a[low:high]
    • lowを含むがhighは含まない(右半開区間)
    • aの要素のうち1から3を含むスライスを作る例: a[1:4]
package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}
  • スライスは配列への参照のようなもの
    • スライスはどんなデータも格納しておらず、単に元の配列の部分列を指し示している
    • スライスの要素を変更すると、その元のなる配列の対応する要素が変更される
package main

import "fmt"

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

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

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}

スライスリテラル

  • 長さのない配列リテラルのようなもの
  • []bool{true, true, false}
    • まず配列 [3]bool{true, true, false}を作成
    • 次にこの配列を参照するスライスを作成
package main

import "fmt"

func main() {
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(q)

	r := []bool{true, false, true, true, false, true}
	fmt.Println(r)

	s := []struct { // 構造体のスライスリテラル
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
}

スライスの既定値

  • 配列var a [10]intにおいて以下のスライスは全て等価
    • a[0:10]
    • a[:10]
    • a[0:]
    • a[:]
package main

import "fmt"

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

	s = s[1:4] // [3 5 7]
	fmt.Println(s)

	s = s[:2] // [3 5]
	fmt.Println(s)

	s = s[1:] // [5]
	fmt.Println(s)
}

スライスの長さと容量

  • 長さ: スライスに含まれる要素の数
    • len(s)で取得
  • 容量: スライスの最初の要素から数えた元の配列の要素数
    • cap(s)で取得
package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s) // len=6 cap=6 [2 3 5 7 11 13]

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s) // len=0 cap=6 []

	// Extend its length.
	s = s[:4]
	printSlice(s) // len=4 cap=6 [2 3 5 7]

	// Drop its first two values.
	s = s[2:]
	printSlice(s) // len=2 cap=4 [5 7]

	// Extend its length more than the capacity
	s = [:5]
	printSlice(s) // error: slice bounds out of range [:5] with capacity 4
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

スライスのゼロ値

  • スライスのゼロ値はnil
  • 0の長さ、0の容量、元の配列を持たない

make関数によるスライスの生成

  • make関数はゼロ化された配列を用意し、その配列を指すスライスを返す
  • 2番目の引数は長さ、3番目の引数は容量
package main

import "fmt"

func main() {
	a := make([]int, 5)
	printSlice("a", a) // len=5 cap=5 [0 0 0 0 0]

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

	c := b[:2]
	printSlice("c", c) // len=2 cap=5 [0 0]

	d := c[2:5]
	printSlice("d", d) // len=3 cap=3 [0 0 0]
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

スライスのスライス

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Create a tic-tac-toe board.
	board := [][]string{ // スライスリテラル、ただし[][]
		[]string{"_", "_", "_"}, // スライスリテラル
		[]string{"_", "_", "_"}, // スライスリテラル
		[]string{"_", "_", "_"}, // スライスリテラル
	}

	// The players take turns.
	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], " "))
	}
}

スライスへの要素の追加

  • スライスへ新しい要素を追加するには、組み込みのappend関数を使う
  • appendの戻り値はスライス
  • 元の配列の容量が小さい場合は、より大きな容量の配列を割り当て直す
    • 戻り値となるスライスは、新しい割当先を示すようになる
package main

import "fmt"

func main() {
	var s []int
	printSlice(s) // len=0 cap=0 []

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s) // len=1 cap=1 [0]

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s) // len=2 cap=2 [0]

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s) // len=5 cap=6 [0 1 2 3 4]
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

Range

  • forループに利用するrangeは、スライスやマップをひとつずつ反復処理するために使う
  • スライスをrangeで繰り返す場合、rangeは反復ごとに2つの変数を返す
    • 1つ目の変数: インデックス
    • 2つ目の変数: インデックスの場所の要素のコピー
package main

import "fmt"

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)
	}
}
  • インデックスや値は_へ代入することで捨てることができる
for i, _ := range pow
for _, value := range pow

// インデックスだけ必要なのであれば
for i := range pow

マップ

マップ(連想配列)とは

  • mapはキーと値とを関連づける
  • ゼロ値はnil
    • nilマップはキーを持っておらず、またキーを追加することもできない
  • make関数は指定された型のマップを初期化して、使用可能な状態で返す
package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex
// var で宣言しただけの時点でキーを追加しようとすると
// panic: assignment to entry in nil map

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"]) // {40.68433 -74.39967}
}

マップリテラル

  • 構造体リテラルに似ているが、キーが必要
package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

func main() {
	fmt.Println(m) // map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
}
  • もしmapに渡すトップレベルの型が単純な型名である場合は、リテラルの要素から推定できるので、その型名を省略することができる
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

マップの操作

  • mapmへの操作
    • 要素の挿入や更新: m[key] = elem
    • 要素の取得: elem = m[key]
    • 要素の削除: delete(m, key)
    • キーに対する要素が存在するかどうかは2つ目の戻り値で確認: elem, ok = m[key]
      • mkeyがある => ok は true
      • mkeyがない => okは false
        • このときelemはmapの要素の型のゼロ値となる
package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"]) // 42

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"]) // 48

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"]) // 0

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok) // 0 false
}

関数

関数値

  • Goの関数は変数である
  • 引数や戻り値にとることができる
package main

import (
	  "fmt"
	  "math"
)

func compute(fn func(float64, float64) float64) float64 {
	// fn: 関数の引数に「関数値」をとることができる
	// fn の引数: float64, float64
	// fn の戻り値: float64
	return fn(3, 4) // 関数値は戻り値としても利用可能
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	} // 関数も変数である(関数値)
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot)) // 関数 compute に関数を渡している
	fmt.Println(compute(math.Pow))
}

クロージャ

  • Goの関数はクロージャである
    • 外部から変数を参照する関数値
      • クロージャは変数へ bind されている
    • 参照された変数にアクセスして変えることができる
package main

import "fmt"

func adder() func(int) int {
	sum := 0 // クロージャに参照されている変数
	return func(x int) int {
		sum += x
		return sum
	} // 変数 sum に bind されたクロージャを返している
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
		// 0 0
		// 1 -2
 		// 3 -6
 		// 6 -12
 		// 10 -20
	}
}

次回

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0