PC依存エンジニアのブログ

開発者向けにIT関連の情報をお届けしています。特にバックエンド開発者向けの内容が多めです。たまにフリーランス情報もお届けしています。

Go言語を学んでいく 第3弾:配列、スライス、マップ

Go言語の基礎を学んでいくシリーズ。
Go言語を学んでいく 第1弾:基礎 - PC依存エンジニアのブログ
Go言語を学んでいく 第2弾:基本的なステートメント - PC依存エンジニアのブログ

第3弾は配列、スライス、マップを見ていきます。

配列

宣言方法

以下のパターンがあります。

  1. var 変数名 [Length]型
  2. var 変数名 [Length]型 = [Length]型 {初期値}
  3. 変数名 := [...]型 {初期値}
  4. 変数名 := new([length]型)

3つ目の[...]の部分は、初期値に合わせて自動的に長さをカウントしてくれることを指します。
またインデックスは0から始まる点に注意です。

サンプルコードは以下のようになります。

package main

import (
    "fmt"
)

func main() {
    var a [3]int
    var b [3]int = [3]int{1, 2, 3}
    c := [...]int{1, 2, 3}
    d := new([3]int)

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
}

長さ

配列の長さは

len(変数名)

で取得することができます。

package main

import (
    "fmt"
)

func main() {
    c := [...]int{1, 2, 3}
    fmt.Println(len(c))
}

スライス

他言語では、あまり聞かないと思います。
スライスは、配列内の要素への参照です。
スライス型は、このスライスを集合を表しています。

宣言方法

以下のパターンがあります。

  1. var 変数名 []型
  2. var 変数名 型 = 型 {初期値}
  3. 変数名 := []型 {初期値}
  4. 変数名 := 配列[開始インデックス:終了インデックス]
  5. 変数名 := make([]型, length, capacity)
  6. 変数名 := new([length]型)[開始インデックス:終了インデックス]

ほとんど配列の宣言と同じですが、長さを指定することはありません。

package main

import (
    "fmt"
)

func main() {
    array := [...]int{1, 2, 3}

    var a []int
    var b []int = []int{1, 2, 3}
    c := []int{1, 2, 3}
    d := array[0:2]
    e := make([]int, 3, 3)
    f := new([3]int)[0:2]

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
    fmt.Println(e)
    fmt.Println(f)
}

要素の追加

append関数を使用することで、簡単に要素を追加することができます。

append(変数名, 値)

ただし、新しいスライスが返ってくるので注意が必要です。

package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 3}
    b := append(a, 4)

    fmt.Println(a)
    fmt.Println(b)
}

メモリの共有

スライスは配列の参照ですので、配列の値が変更されると、スライスが示す値も変更されます。
逆にスライス経由で値を変更すれば、参照先の配列の値も変更されます。

package main

import (
    "fmt"
)

func main() {
    array := [...]int{1, 2, 3}

    a := array[0:]
    fmt.Println(array, a)

    array[0] = 4
    fmt.Println(array, a)

    a[0] = 1
    fmt.Println(array, a)
}

LengthとCapacity

少々紛らわしいですが、スライスには長さを示すlengthと、容量を示すcapacityがあります。
lengthは、スライスの要素数です。
capacityは参照先の配列の要素数です。

長さと容量はそれぞれ、len関数とcap関数で取得することができます。

package main

import (
    "fmt"
)

func main() {
    array := [...]int{1, 2, 3}

    a := array[0:1]
    fmt.Println(len(a))
    fmt.Println(cap(a))
}

マップ

JavaとかならHashMap、C#pythonならDictionaryに相当する部分です。
ユニークになるキーと、値を管理しています。
なお要素の順序は持っていません。

宣言方法

以下のパターンがあります。

  1. make(map[キーの型]値の型)
  2. make(map[キーの型]値の型, 容量の初期値)
  3. map[キーの型]値の型 {初期値}

容量の初期値は省略可能です。

package main

import (
    "fmt"
)

func main() {
    a := make(map[int]string)
    b := make(map[int]string, 2)
    c := map[int]string{1: "one", 2: "two"}

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}


補足になりますが、make関数で容量の初期値を指定しても、その容量以上登録できないわけではありません。
上の例であれば、変数bに2個以上要素を登録できます。
容量オーバになったら、自動で容量を拡張してくれるわけです。

だったら初期値を設定する意味ないじゃんとなりそうですが、
容量の初期値を設定するメリットは、処理を高速にするためです。
map容量を拡張するとき、元データのコピーと再ハッシュが行われます。

つまり容量を確保していない場合、毎回元データのコピーと再ハッシュが行われてしまい、処理負担が大きくなります。
だったら最初からある程度の容量を確保しておいた方が、処理の高速化につながります。
だからといって容量をとりすぎるのはダメですよ。。

要素の挿入、更新

挿入と更新は、キーと値を指定して、登録します。
この時すでにキーが登録されていれば、更新となり、
キーが未登録であれば、挿入となります。

変数名[key] = 値
package main

import (
    "fmt"
)

func main() {
    a := map[int]string{1: "one", 2: "two"}
    fmt.Println(a)

    a[3] = "Three"
    fmt.Println(a)

    a[1] = "iti"
    fmt.Println(a)
}

要素の取得

キーを指定して、取得できます。

変数名[key]

なお存在しないキーを指定され場合、デフォルト値が返却されますので注意が必要です。

package main

import (
    "fmt"
)

func main() {
    a := map[int]string{1: "one", 2: "two"}

    fmt.Println(a[1])
    fmt.Println(a[10] == "")
}

要素の削除

要素の削除はdelete関数で行います。

delete(変数名, key)
package main

import (
    "fmt"
)

func main() {
    a := map[int]string{1: "one", 2: "two"}
    fmt.Println(a)

    delete(a, 2)
    fmt.Println(a)
}

次回

第4弾ではポインタと構造体を見ていきます。