Open9

Golang まとめ

Goにおける型

組み込み型

種類 型名
整数 int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, byte, rune
浮動小数点 float32, float64
複素数 complex64, complex128
文字列 strin
真偽値 bool
エラー error

コンポジット型

コンポジット型とは複数のデータを1つの集合として表す型になります。

構造体

0個以上の変数を集合させたデータ構造
構造体内の変数のことをフィールドと呼ぶ

// フィールドがない構造体
var empty struct{}

// フィールド3つ
var point struct {
  ID   string
  x, y int
}

配列

同じ型のデータを並べたデータ構造
固定長のため、データの追加ができません。

// ゼロ値で初期化
var array [5]int

// 要素を定義
nums := [3]int{1, 2, 3}

// 要素数から配列数を推論
numbers := [...]int{1, 2, 3, 4, 5}

スライス

同じ型のデータを並べたデータ構造
可変長!

var slice []int
nums := []int{1,2,3}

マップ

キーと値を組み合わせたデータ構造

var m map[string]int

user := map[string]int {
  "hoge": 23,
  "fuga": 12,
}

スライスの操作

len, capを用いることでスライスの長さや容量を取得することができる。
長さ4, 容量:4 のスライスに対してアペンドすると、長さ: 5, 容量: 8になっていることがわかる。
容量が足りなくなると、基本的に2倍の容量のメモリ領域を再確保する。

package main

import "fmt"

func main() {

	nums := []int{1, 2, 3, 4}

	// [1 2 3 4]
	fmt.Println(nums)
	// 4
	fmt.Println(len(nums))
	// 4
	fmt.Println(cap(nums))

	nums = append(nums, 5)
	// [1 2 3 4 5]
	fmt.Println(nums)
	// 5
	fmt.Println(len(nums))
	// 8
	fmt.Println(cap(nums))

}

makeを使ったスライスの初期化

長さと容量が指定できて便利。

package main

import "fmt"

func main() {

	// len: 2, cap: 3
	slice := make([]int, 2, 3)

	// [0, 0]
	fmt.Println(slice)
	// 2
	fmt.Println(len(slice))
	// 3
	fmt.Println(cap(slice))

}

sliceのn番目の要素を削除

appendを用いて i番目までの要素と i+1番目からの要素をくっつけたものを作成すると削除となります。
(削除ですが少し面倒ですね...)

package main

import "fmt"

func main() {

	nums := []int{0, 1, 2, 3, 4, 5}

	i := 2
	// [0, 1]
	fmt.Println(nums[:i])
	// [3, 4, 5]
	fmt.Println(nums[i+1:])

	result := append(nums[:i], nums[i+1:]...)

	// [0, 1, 3, 4, 5]
	fmt.Println(result)

}

... はスライスを展開して引数に渡しています。

https://golang.org/ref/spec#Passing_arguments_to_..._parameters

スライスを逆順に並べる

package main

import "fmt"

func main() {

	nums := []int{0, 1, 2, 3, 4, 5}

	for i := len(nums)/2 - 1; i >= 0; i-- {
		opp := len(nums) - 1 - i
		nums[i], nums[opp] = nums[opp], nums[i]
	}

	// [5, 4, 3, 2, 1, 0]
	fmt.Println(nums)

}

スライス中央の値から
左と右を交換していく感じで面白いですね

マップの操作

値の初期化

empty := map[string]int{}
mapMake := make(map[string]int)
mapCap := make(map[string]int, 10)

key, valueの存在有無取得

他の言語で言う、 map.contains() みたいなものも同じ書き方で取得できます。
シンプルで良いですね..!

m := map[string]int{
  "test1": 11,
  "test2": 22,
}

test1Num := m["test1"]
// 11
fmt.Println(test1Num)

test1Num, ok := m["test1"]
if ok {
  // true
  fmt.Println(ok)
  // 11
  fmt.Println(test1Num)
}

_, ok = m["hogehoge"]
// false
fmt.Println(ok)

マップを用いてユニークなスライスを生成

マップはキーをセットとして保持する特性があるため、
重複チェックに使えたりします。

空の構造体を用いることでメモリ要領を取りません。

A struct{} takes up no space.

ユニークなスライスを生成。

package main

import (
	"fmt"
)

func main() {

	nums := []int{1, 2, 3, 4, 4, 5, 5, 6}
	unique := make([]int, 0, len(nums))

	tmpMap := make(map[int]struct{})
	for _, v := range nums {
		if _, ok := tmpMap[v]; ok {
			continue
		}
		unique = append(unique, v)
		tmpMap[v] = struct{}{}
	}
	// [1, 2, 3, 4, 5, 6]
	fmt.Println(unique)
}

レシーバ

メソッド: レシーバと紐づけられている関数のこと
レシーバ: メソッドに紐づけられている変数

レシーバを型にするかポインタにするかで挙動が変わります。
いわゆる値渡し or 参照渡し です。

package main

import "fmt"

type Counter struct {
	Num int
}

func (c *Counter) Count() {
	c.Num++
}

func (c Counter) CountDryRun() {
	c.Num++
}

func main() {

	counter := &Counter{
		Num: 0,
	}

	// 0
	fmt.Println(counter.Num)

	counter.Count()
	// 1
	fmt.Println(counter.Num)

	counter.CountDryRun()
	counter.CountDryRun()
	// 1
	fmt.Println(counter.Num)
}

どちらにするかはざっくり以下の3つを考えると良さそうです。

  • 呼び出し元の値を変える必要があるなら、ポインタ
  • レシーバが大きければ、ポインタ
  • レシーバが小さければ、値
ログインするとコメントできます