この記事を書いた目的
qiitaの記事でスライスについての記事を眺めていて
sliceってなんぞや!
記事を見てもちょっと分からないということで
こちらを実際に解説しながら
capとlen、スライスについて
説明してみました。
そして自分は新卒プログラマーでGo歴1ヶ月目なので指摘等あれば幸いです。
⭐️スライスとは?
可変長配列を持たない代わりに実装された型。
配列全体のポインタ(ptr)、配列の長さ(len)、容量(cap)からなるデータ構造を保持している。
●型の宣言
var hoge []int
var hoge []int{1, 2, 3}
hoge := []int{1, 2, 3}
●動作確認
# 【slice.go】 #
package main
import (
"fmt"
)
func main() {
a := [5]string{"ドラえもん", "のび太", "しずかちゃん", "ジャイアン", "スネ夫"}
b := a[:]
c := a[2:4]
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
b[1] = "ドラミ"
c[0] = "出木杉"
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
# 【出力結果】 #
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫]
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫]
[しずかちゃん ジャイアン]
[ドラえもん ドラミ 出木杉 ジャイアン スネ夫]
[ドラえもん ドラミ 出木杉 ジャイアン スネ夫]
[出木杉 ジャイアン]
💡解説
まず取り出し方について事前解説
例 | 解説 |
---|---|
a[:] | 全要素 |
a[2:] | 2番目後から最後まで |
a[2:4] | 2番目後から4番目まで |
a[:5] | 初めから5番目まで |
a := [5]string{}
まず、aという配列に
- ドラえもん
- のび太
- しずかちゃん
- ジャイアン
- スネ夫
の5人が入りました
b := a[:]
bはaの全取り出しなので
- ドラえもん
- のび太
- しずかちゃん
- ジャイアン
- スネ夫
aと同じように表示されます
c := a[2:4]
cはaの2番目後から4番目を取り出しているので
- しずかちゃん
- ジャイアン
が表示
ここまでは普通に理解できますね。
b[1] = "ドラミ"
c[0] = "出木杉"
をすることによってなぜ
表示結果がaにも反映されるのか?
それはスライスに渡した部分列の要素は
元の配列のメモリ領域が共有されている為。
スライス型で要素を代入するということは
元の配列データに代入するということ。
よって
上記のコード
b[1] = "ドラミ"
c[0] = "出木杉"
によってb
とc
に代入すると
a
にも反映されるということである。
⭐️組み込み関数
スライスでは組み込み関数として
- make()
- append()
が使える。
⭐️make関数
make(型, 要素数, 容量)
容量を省略すると、要素数==容量となる
●動作確認
# 【make.go】 #
package main
import (
"fmt"
)
func main() {
var a []int
a = make([]int, 5, 10)
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
for i, _ := range a {
a[i] = i
}
fmt.Println(a)
b := make([]int, 10)
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
}
# 【出力結果】 #
[0 0 0 0 0]
5
10
[0 1 2 3 4]
[0 0 0 0 0 0 0 0 0 0]
10
10
💡解説
make関数は
make(型, 要素数, 容量)で表現します
また、容量を省略すると要素数==容量となります。
上記でも述べている通りです。
[a]
a = make([]int, 5, 10)
型(int)
要素数(5)
容量(10)
ということが分かります。
なので表示は
[0 0 0 0 0] //(a)
5 //len(a)
10 //cap(a)
[b]
b := make([]int, 10)
容量を省略しているので
型(int)
要素数(10)
容量(10)
ということが分かります。
なので表示は
[0 0 0 0 0 0 0 0 0 0]
10
10
⭐️append関数
append(スライス, データ1, データ2, ...)
指定したスライスの末尾に追加
スライスの要素数は自動で増えていく
●動作確認
# 【append.go】 #
package main
import (
"fmt"
)
func main() {
a := []string{"ドラえもん", "のび太", "しずかちゃん", "ジャイアン", "スネ夫"}
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
a = append(a, "ミニドラ", "出木杉")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
a = append(a, "ジャイ子", "のび助", "玉子", "スネ吉")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
}
# 【出力結果】 #
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫]
5
5
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ミニドラ 出木杉]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ミニドラ 出木杉 ジャイ子 のび助 玉子 スネ吉]
11
20
💡解説
最大の特徴は
容量はいっぱいになった時、
現在の 容量を倍 にして確保してくれます。
配列のメモリアドレスは容量の拡張に伴い変更されます。
それを踏まえて解説します。
[パート1]
【append.go】
a := []string{"ドラえもん", "のび太", "しずかちゃん", "ジャイアン", "スネ夫"}
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
【出力】
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫]
5
5
これであれば分かりやすいですね
aには5人のキャラクターがいるので
len(5)
cap(5)
となります
[パート2]
【append.go】
a = append(a, "ミニドラ", "出木杉")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
【出力】
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ミニドラ 出木杉]
7
10
指定が無いので末尾に追加されているのが分かります。
capに注目してみましょう。
一番最初は
len(5)
cap(5)
であったaですが
最初のlenの長さをオーバーした為
自動で倍に容量を増やしてくれているのが分かります。
パート3も同様です。
『ではもし容量が確保されていないとどうなる???
容量って気にする必要ある?』
という事で実験をしてみましょう。
🙆容量が確保されている場合
前提条件:要素数7,容量10
●動作確認
# 【test1.go】 #
package main
import (
"fmt"
)
func main() {
a := make([]string, 7, 10)
a[0] = "ドラえもん"
a[1] = "のび太"
a[2] = "しずかちゃん"
a[3] = "ジャイアン"
a[4] = "スネ夫"
a[5] = "ミニドラ"
a[6] = "出木杉"
b := append(a[:5], "ジャイ子", "のび助")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
b = append(a[:5], "玉子", "スネ吉", "パーマン", "ジャイ子", "のび助")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
b[5] = "サザエさん"
b[6] = "しんのすけ"
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
}
# 【出力結果】 #
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 玉子 スネ吉]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 玉子 スネ吉 ジャイ子 のび助]
9
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 サザエさん しんのすけ]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 サザエさん しんのすけ ジャイ子 のび助]
9
10
💡解説
[パート1]
【コード】
b := append(a[:5], "ジャイ子", "のび助")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
【出力】
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
10
まずは
b := append(a[:5], "ジャイ子", "のび助")
この一文が入りますので
bにはaの5番目までとジャイ子+のび助が入ります。
そして
出力結果ですが
上記でも述べているように
スライスに渡した部分列の要素は
元の配列のメモリ領域が共有されている為。スライス型で要素を代入するということは
元の配列データに代入するということ。
の為bに入れた値はaにも反映される為
出力が同じになっています
[パート2]
【コード】
b = append(a[:5], "玉子", "スネ吉", "パーマン", "ジャイ子", "のび助")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
【出力】
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 玉子 スネ吉]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 玉子 スネ吉 ジャイ子 のび助]
9
10
まずは
b = append(a[:5], "玉子", "スネ吉", "パーマン", "ジャイ子", "のび助")
この一文にてbはaの5番目までと
玉子+スネ吉+パーマン+ジャイ子+のび助が入ります
つまりbは
ドラえもん のび太 しずかちゃん ジャイアン スネ夫 玉子 スネ吉 ジャイ子 のび助
になります。
[パート3]
【コード】
b[5] = "サザエさん"
b[6] = "しんのすけ"
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
【出力】
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 サザエさん しんのすけ]
7
10
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 サザエさん しんのすけ ジャイ子 のび助]
9
10
まずは
b[5] = "サザエさん"
b[6] = "しんのすけ"
これはもう分かりますね。
ここで言いたいのは
配列のメモリを共有しているので、
aとbは同じデータ構造であり
容量に変化はないため
確保されている要素では共有状態にあると言える。ということです。
十分な容量を確保せずにappendした場合
自動的に容量を倍にする為、配列のメモリアドレスが変更されてしまいます。
そうなってしまうと
aとbは独立されたスライスとなってしまい
データの編集・追加をしても
共有されることはなくなってしまいます。
実例をみてみましょう
先ほどのコードを3文字消しただけです
(容量部分を消しただけ)
🙅容量が確保されていない場合
前提条件:要素数7,容量7
●動作確認
# 【test2.go】 #
package main
import (
"fmt"
)
func main() {
a := make([]string, 7)
a[0] = "ドラえもん"
a[1] = "のび太"
a[2] = "しずかちゃん"
a[3] = "ジャイアン"
a[4] = "スネ夫"
a[5] = "ミニドラ"
a[6] = "出木杉"
b := append(a[:5], "ジャイ子", "のび助")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
b = append(a[:5], "玉子", "スネ吉", "ジャイ子", "のび助")
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
b[5] = "サザエさん"
b[6] = "しんのすけ"
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
fmt.Println(b)
fmt.Println(len(b))
fmt.Println(cap(b))
}
# 【出力結果】 #
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
7
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
7
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
7
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 玉子 スネ吉 ジャイ子 のび助]
9
14
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 ジャイ子 のび助]
7
7
[ドラえもん のび太 しずかちゃん ジャイアン スネ夫 サザエさん しんのすけ ジャイ子 のび助]
9
14
このようにそれぞれが独立したスライスになって
sliceの強みが無くなってしまうのであれば
コピーでいいじゃんということになってしまいます。