LoginSignup
7
7

More than 5 years have passed since last update.

【Go言語】新卒がまとめるスライスについて

Posted at

この記事を書いた目的

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] = "出木杉"

によってbcに代入すると
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の強みが無くなってしまうのであれば
コピーでいいじゃんということになってしまいます。

参考

Go言語 スライス確認
Go by Example: Slices

7
7
0

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