スライスはGo言語の中でもっとも利用されるデータ構造だと思いますが、挙動が複雑で仕様を把握するのが難しく、利用頻度が高いがゆえによく分からず使っているとパフォーマンスの低下を招き易い機能と言えるかと思います。
スライス及び配列の特徴を把握することで、より実行効率の高いGoのコードを書けるようになるようになります。より詳細な情報を知りたい方は、以下の公式ブログのドキュメントを読まれることをお勧めします。
- Arrays, slices (and strings): The mechanics of 'append' - The Go Programming Language
- Go Slices: usage and internals - The Go Programming Language
配列とスライスの特徴、違い
配列もスライスも、以下のような特定のデータ型(String等)の値を一定の長さの分だけ格納できるデータの入れ物のようなものができる点は同じです。
それぞれの特徴としては、主に
- ①長さが固定か可変か
- ②参照型かどうか
2つのが挙げられます。
①長さが固定か可変か
一番大きな違いとしては、最初に変数を定義した後、要素を増やしたりできるかどうかという点です。
- 配列(固定長配列)
- スライス(可変長配列)
以下で例を示しますが、配列・スライスのそれぞれの定義の仕方の相違点は
- 配列: 長さの部分が固定または省略表記(etc. [3], [...])
- スライス: 長さの部分が未指定、またはmakeで生成
という部分です。
【配列の場合】
// 長さの部分を3に固定して配列を定義 people := [3]string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", } // 定義後長さを変えられず、要素を増やせない。appendできない。 // [エラー] => people = append(people, "竈門 禰豆子(かまど ねずこ)") // 省略表記で配列を定義 people2 := [...]string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", } // peopleとpeople2は内容が同じ配列 reflect.DeepEqual(people, people2) => true
【スライスの場合】
// 長さの部分を未指定にしてスライスを定義 people := []string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", } // 定義した後追加できる people = append(people, "竈門 禰豆子(かまど ねずこ)") // makeを使って長さが4のスライスを定義 people2 := make([]string, 4) people2[0] = "竈門 炭治郎(かまど たんじろう)" people2[1] = "我妻 善逸(あがつま ぜんいつ)" people2[2] = "嘴平 伊之助(はしびら いのすけ)" people2[3] = "竈門 禰豆子(かまど ねずこ)" // peopleとpeople2は内容が同じスライス reflect.DeepEqual(people, people2) => true
②参照型かどうか
2つ目の違いは参照型かどうかです。配列とスライスは、定義の仕方がちょっと違うだけですが、関数に渡した時や変数に代入した時の挙動が異なります。
スライスは参照型であり、例えば関数の引数として指定した場合、参照が渡るため関数の中で値が変更されていれば、関数の外でも変更が反映されます。
配列の場合は、値渡しになるため関数内での変更が、関数の外に反映されません。(参照渡し・値渡しの違いに関しては別の記事で記載しています。)
【配列(値渡し)の場合】
func ChangeToNezuko(people [3]string) { people[0] = "竈門 禰豆子(かまど ねずこ)" } peopleArray := [3]string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", } // 関数の中で、配列の1つ目の要素"竈門 炭治郎(かまど たんじろう)"を // "竈門 禰豆子(かまど ねずこ)"に変えようとしてみる ChangeToNezuko(peopleArray) // 関数の外には変更が反映されない fmt.Println(peopleArray[0]) => "竈門 炭治郎(かまど たんじろう)"
【スライス(参照渡し)の場合】
func ChangeToNezuko(people []string) { people[0] = "竈門 禰豆子(かまど ねずこ)" } peopleSlice := []string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", } // 関数の中で、スライスの1つ目の要素"竈門 炭治郎(かまど たんじろう)"を // "竈門 禰豆子(かまど ねずこ)"に変えてみる ChangeToNezuko(peopleSlice) // 関数の外でも変更が反映されている fmt.Println(peopleSlice[0]) => "竈門 禰豆子(かまど ねずこ)"
なお、配列の場合でも、関数への渡し方を変えて、配列のポインタを関数の引数にすることで、スライス同様に関数内の変更を外に反映させることができます。
【配列(参照渡し)の場合】
func ChangeToNezuko(people *[3]string) { people[0] = "竈門 禰豆子(かまど ねずこ)" } peopleArray := [3]string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", } // 関数の中で、配列の1つ目の要素"竈門 炭治郎(かまど たんじろう)"を // "竈門 禰豆子(かまど ねずこ)"に変えてみる ChangeToNezuko(&peopleArray) // 関数の外でも変更が反映されている fmt.Println(peopleArray[0]) => "竈門 禰豆子(かまど ねずこ)"
スライスと配列の構造的な相違
スライスの構造は reflect.SliceHeader で確認することができるのですが、配列のラッパーになっていて以下の構造になっています。
type SliceHeader struct { Data uintptr Len int Cap int }
それぞれ
- Dataには、配列のポインタが格納されて
- Lenには、長さ
- Capには、容量
が保存されています。
図にすると以下のようになります。
コード上でLen(長さ)、Cap(容量)を確認する際は、 len()
で cap()
の引数にスライスを指定します。
people := []string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", } len(people) => 3 cap(people) => 3
長さを未指定にすることで、長さと容量が同じスライスを生成することができますが、 make
を使うことで長さと容量が異なるスライスを作ることができます。
メモリ領域の確保は比較的重い処理なので、定義の時点で必要な領域(容量)を事前に確保しておくことで、パフォーマンスの高いプログラムを実装できます。
make
の1番目の引数でデータ型、2番目の引数でスライスの長さ、3番目の引数で容量を指定します。
people := make([]string, 3, 4) people[0] = "竈門 炭治郎(かまど たんじろう)" people[1] = "我妻 善逸(あがつま ぜんいつ)" people[2] = "嘴平 伊之助(はしびら いのすけ)" fmt.Println(people) => [竈門 炭治郎(かまど たんじろう) 我妻 善逸(あがつま ぜんいつ) 嘴平 伊之助(はしびら いのすけ)] // スライス定義時と同じ長さ、容量 len(people) => 3 cap(people) => 4 // 長さ3なので、4番目の要素に値を入れようとするとエラーになる // people[3] = "竈門 禰豆子(かまど ねずこ)" // => panic: runtime error: index out of range [3] with length 3 // appendで要素を追加できる people = append(people, "竈門 禰豆子(かまど ねずこ)") []string{ "竈門 炭治郎(かまど たんじろう)", "我妻 善逸(あがつま ぜんいつ)", "嘴平 伊之助(はしびら いのすけ)", "竈門 禰豆子(かまど ねずこ)", } // 要素を追加したことで長さが4、容量が4になる len(people) => 4 cap(people) => 4
追記
スライスの様々な操作 ( 簡易スライス式 / copy / append ) の挙動に関しては他の記事にまとめてみました。もし、よろしければこちらもご参照ください。 simple-minds-think-alike.hatenablog.com