LoginSignup
9
6

More than 5 years have passed since last update.

Goのarrayとsliceって値なの?ポインタなの?

Last updated at Posted at 2018-09-22

Goのarrayとsliceについて説明しようとしたときに、自分でも混乱して説明できないことがあったので、もう一度調査してまとめてみました
あとは自分が過去にsliceでハマったところも記載してます

まずはarrayを理解しよう

Go Slices: usage and internalsにも書いてありますが、sliceを理解するにはまず配列について理解する必要があります

Goの配列は値である

Goの配列は値であり、C言語のように配列の先頭要素へのポインタではありません
その為、例えば関数の引数で配列を渡したり別の変数にいれたりすると、その配列のコピーが作成されます
関数内で配列の中身を変更したい場合は、配列のポインタを渡しましょう

var a = [4]int{1, 2, 3, 4}
fmt.Printf("%#v\n", a) // [4]int{1, 2, 3, 4}
fmt.Printf("%p\n", &a) // 0xc42009c000

b := a
b[0] = 0
fmt.Printf("%#v\n", b) // [4]int{0, 2, 3, 4}
fmt.Printf("%p\n", &b) // 0xc4200161a0

fmt.Printf("%#v\n", a) // [4]int{1, 2, 3, 4}

sliceは値?ポインタ?

sliceの実体はarrayへのポインタ、しかしそれだけではない

sliceはarrayへのポインタを保持しています
しかしarrayのポインタそのものではなく、arrayのポインタ、length、capacityを保持している構造体です

runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

arrayのポインタを保持しているため、他の変数に渡して要素の値を変更したら元の要素の値も変わります

a := make([]int, 1)
fmt.Printf("%#v\n", a) // []int{0}

b := a
b[0] = 1
fmt.Printf("%#v\n", b) // []int{1}

fmt.Printf("%#v\n", a) // []int{1}

sliceの宣言

var s []Tでsliceを宣言すると、sliceの構造体の中でarrayへのポインタを保持していないのでsliceはnilになります
この場合、slice == niltrueになるほか、jsonへエンコードしてもnullとなります
make([]T, 0)でsliceを宣言した場合は、nilにはなりません
jsonへエンコードすると空の配列になります
ちなみにlen()はどちらの場合も0になります

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    var a []int
    fmt.Printf("%#v: len = %d, isNil = %v, json = %s\n", a, len(a), isNil(a), marshal(a))
    // []int(nil): len = 0, isNil = true, json = null

    b := make([]int, 0)
    fmt.Printf("%#v: len = %d, isNil = %v, json = %s\n", b, len(b), isNil(b), marshal(b))
    // []int{}: len = 0, isNil = false, json = []
}

func isNil(s []int) bool {
    return s == nil
}

func marshal(s []int) string {
    bytes, err := json.Marshal(s)
    if err != nil {
        log.Fatal(err)
    }
    return string(bytes)
}

capacityは指定したほうが良い?

make([]T, 0)でcapacityを宣言しないと、capacityはlengthと同じになります
append()でcapacityを超えても要素を追加できるから、常にcapacity省略してもいいのでは?と思いますが(私は昔そう思ってました。。。)それは間違いです
capacityを超えてappendする場合は、メモリアロケーションが発生しパフォーマンスが悪くなります

package main

import "testing"

func BenchmarkAppendNoCapacitySpecified(b *testing.B) {
    s := make([]int, 0)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        s = append(s, i)
    }
}

func BenchmarkAppendCapacitySpecified(b *testing.B) {
    s := make([]int, 0, b.N)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        s = append(s, i)
    }
}
BenchmarkAppendNoCapacitySpecified-4    100000000               42.6 ns/op            49 B/op          0 allocs/op
BenchmarkAppendCapacitySpecified-4      2000000000              12.1 ns/op             0 B/op          0 allocs/op

ベンチマークを見てもcapacityを指定したほうがパフォーマンスが良いのは明らかです
要素数の最大値がわかっているときはcapacityを指定しましょう

まとめ

  • Goのarrayは値
  • Goのsliceはarrayへのポインタ、length、capacityを保持した構造体
  • sliceはnilになるケースがある
  • capacityはなるべく指定する

参考

9
6
2

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
9
6