概要
今回のブログポストではGolangのSlice
(スライス)について、詳しく説明して使う方法も説明します。このブログポストで紹介するコードは次のリンクで確認できます。
Slice
スライスはGolangが提供する動的配列タイプ(配列を指定するポインタタイプ)です。
- 性的(Static): コンパイル時点(Compile)で決定
- 動的(Dynamic): 実行時点(Runtime)で決定
次はGolangで配列を宣言する方法です。
var v [10]int
スライスは次のように配列のサイズを設定しなくて宣言します。
var v []int
これを確認するためmain.go
ファイルを生成して次のように修正します。
package main
import "fmt"
func main() {
var a [2]string
var b []string
fmt.Println(a)
fmt.Println(b)
}
これを実行すると次のような結果が表示されます。
# go run main.go
[ ]
[]
配列とスライス
スライスは配列を指定するポインタタイプです。これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
func changeArr(arr2 [5]int) {
arr2[0] = 100
}
func changeSlice(slice2 []int) {
slice2[0] = 100
}
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := []int{1, 2, 3, 4, 5}
changeArr(arr)
changeSlice(slice)
fmt.Println(arr)
fmt.Println(slice)
}
これを実行すると下記のような結果が表示されます。
# go run main.go
[1 2 3 4 5]
[100 2 3 4 5]
配列はポインタタイプではないので、changeArr
関数をコールする時、arr2
と言う新しいインスタンスが生成れて、arr
の内容がコピーされます。したがって、changeArr
関数名kでarr2
の値を変更してもarr
の値は変更されないです。
しかし、スライスはポインタタイプです。したがって、changeSlice
関数をコールすると、slice2
と言う新しいインスタンスを生成するではなく、slice
のメモリアドレスをコピーすることになります。したがって、changeSlice
関数中でslice2
の値を変更するとslice
の値も変更されます。
lenとcap
スライスは配列とは違ってlen
以外にもcap
と言うデータを持っています。スライスのlen
は現在スライスで使っているサイズを意味して、cap
は現在スライスの総サイズ(使ってるサイズ+開いてるサイズ)を意味します。
これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 2, 10)
fmt.Printf("slice1(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
}
これを実行すると下記のような結果が表示されます。
# go run main.go
slice1(0xc0000b2000): len=5 cap=5 [1 2 3 4 5]
slice2(0xc0000b4000): len=2 cap=10 [0 0]
make関数
Golangで提供するmake
関数を使ってスライスを生成することが出来ます。
slice1 := make([]int, 10)
fmt.Println(slice1)
上のようにmake関数を使って10のサイズを持ってるスライスを生成することが出来ます。
slice2 := make([]int, 2, 10)
fmt.Println(slice2)
または、上のようにmake関数を使って10のサイズを持ってるスライスを生成して2つの要素だけ使えるようにすることが出来ます。
これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
func main() {
slice1 := make([]int, 10)
fmt.Println(slice1)
slice2 := make([]int, 2, 10)
fmt.Println(slice2)
}
これを実行すると次のような結果が表示されます。
# go run main.go
[0 0 0 0 0 0 0 0 0 0]
[0 0]
スライシング
配列の一部を取り出してスライスを作ることをスライシング(Slicing)と言います。
- Array => Slicing => Slice
Golangでスライシングは次のように使えます。
Array[startIndex:endIndex]
startIndexからendIndexの直前
(endIndex -1)までの値をリターンします。この時、リターンされたSliceのcap
はstartIndexからArrayの最後の最後までのサイズです。
これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
func main() {
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:2]
fmt.Printf("array: len=%d %v\n", len(array), array)
fmt.Printf("slice: len=%d cap=%d %v\n", len(slice), cap(slice), slice)
}
これを実行すると次のような結果が表示されます。
# go run main.go
array: len=5 [1 2 3 4 5]
slice: len=1 cap=4 [2]
スライスをスライシングしてスライスを生成することが出来ます。また、次のようにstartIndex
に0を指定すると最初からスライシングをすることが出来ます。ここで0
は省略することが出来ます。
slice = []int{1, 2, 3, 4, 5}
slice1 := slice[0:3]
slice2 := slice[:3]
次のように使う場合、最後までスライシングをすることが出来ます。ここで最後のindex省略することが出来ます。
slice = []int{1, 2, 3, 4, 5}
slice1 = slice[2:len(slice)]
slice2 = slice[2:]
最後に、次のように全てをスライシングすることが出来ます。これは配列をスライスに変換する時よく使えます。
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[:]
スライシングは新しい変数を作るのではなく、単純にメモリアドレスを指定するだけなので次のように使うことが出来ます。
array1 := [100]int{1: 1, 2: 2, 99: 100}
slice1 = array1[1:10]
slice2 = slice1[2:99]
fmt.Println(slice1)
fmt.Println(slice2)
上の例題ようにslice1
はarray1
のメモリアドレスを指定してますので、slice1
で99までの値を取り出してslice2
を作ることが出来ます。
capサイズを調整してスライシング
Arrayをスライシングすると、リターンされたSliceのcap
はstartIndexからArrayの最期までのサイズです。しかし、次のようにスライシングする時、maxIndex
を追加してcapのサイズを調整することが出来ます。
slice[startIndex:endIndex:maxIndex]
slice1 = []int{1, 2, 3, 4, 5}
slice2 = slice1[1:3:4]
fmt.Printf("slice1: len=%d cap=%d %v\n", len(slice1), cap(slice1), slice1)
fmt.Printf("slice2: len=%d cap=%d %v\n", len(slice2), cap(slice2), sl
append関数
スライスの最後に要素を追加するためにはappend
関数を使う必要があります。append
関数を使ってスライスに要素を追加する場合、新しい要素が追加されたスライスがリターンされます。
var slice1 = []int{1, 2, 3}
slice2 := append(slice1, 4)
この時、新しいスライスは既存のスライスと同じメモリアドレスを使う時も、同じメモリアドレスを使わない場合もあります。
append
を使って新しい要素をスライスに追加する時、スライスに新しい要素を追加する空間がある場合、新しいスライスは既存のスライスのメモリアドレスを使うことになります。しかし、既存スライスに空間が足りない場合、append
は新しいメモリアドレスに既存スライスをコピーした後、新しい要素を追加することになります。
これを確認するため、main.go
ファイルを次のように修正します。
package main
import "fmt"
func main() {
slice1 := make([]int, 3)
slice2 := append(slice1, 4)
fmt.Println("[New splice]")
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice1 changed ========================================================")
slice1[0] = 100
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice2 changed ========================================================")
slice2[0] = 200
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
slice1 = make([]int, 1, 3)
slice2 = append(slice1, 4)
fmt.Println("[Same slice]")
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice1 changed ========================================================")
slice1[0] = 100
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice2 changed ========================================================")
slice2[0] = 200
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
}
これを実行すると次のような結果が確認できます。
# go run main.go
[New splice]
slice(0xc00012a000): len=3 cap=3 [0 0 0]
slice2(0xc00012c000): len=4 cap=6 [0 0 0 4]
slice1 changed ========================================================
slice(0xc00012a000): len=3 cap=3 [100 0 0]
slice2(0xc00012c000): len=4 cap=6 [0 0 0 4]
slice2 changed ========================================================
slice(0xc00012a000): len=3 cap=3 [100 0 0]
slice2(0xc00012c000): len=4 cap=6 [200 0 0 4]
[Same slice]
slice(0xc00012a018): len=1 cap=3 [0]
slice2(0xc00012a018): len=2 cap=3 [0 4]
slice1 changed ========================================================
slice(0xc00012a018): len=1 cap=3 [100]
slice2(0xc00012a018): len=2 cap=3 [100 4]
slice2 changed ========================================================
slice(0xc00012a018): len=1 cap=3 [200]
slice2(0xc00012a018): len=2 cap=3 [200 4]
スライスのcap
に新しい要素を追加する余裕がある場合、同じメモリアドレスを使うことが確認できます。したがって、スライスのappend
を使う時には、既存のスライスと新しいスライスが同時に変更されることを気をつける必要があります。
スライスのコピー
次のようにスライシングを使ってスライスをコピーすることが出来ます。
slice1 = []int{1, 2, 3, 4, 5}
slice2 = slice1[:]
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
しかし、スライシングは新しいスライスを生成することではなくメモリアドレスを共有することなので、上のようにslice2
の値を変更するとslice1
の値も変更されます。
この問題を解決するためには次のようにslice1
と同じサイズあのslice2
を作って、ループで値をコピーします。
slice1 = []int{1, 2, 3, 4, 5}
slice2 = make([]int, len(slice1))
for i, v := range slice1 {
slice2[i] = v
}
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
または、次のようにappend
関数を使って新しいスライスを作って、全ての内容を追加してコピーすることも出来ます。
slice1 = []int{1, 2, 3, 4, 5}
slice2 = append([]int{}, slice1...)
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
最後に、make
関数を作って同じサイズのスライスを生成した後、copy
関数を使って全ての値をコピーすることも出来ます。
slice1 = []int{1, 2, 3, 4, 5}
slice2 = make([]int, len(slice1))
copy(slice2, slice1)
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
Golangでcopy
関数は次のように使うことが出来ます。
copy(dst, src)
削除
次のようにスライスで特定要素を削除することが出来ます。
slice := []int{1, 2, 3, 4, 5, 6}
deleteIdx := 2
fmt.Println(slice)
for i := deleteIdx + 1; i < len(slice); i++ {
slice[i-1] = slice[i]
}
slice = slice[:len(slice)-1]
fmt.Println(slice)
または、次のようにappend
を使って削除することもできます。
slice = []int{1, 2, 3, 4, 5, 6}
deleteIdx = 2
fmt.Println(slice)
slice = append(slice[:deleteIdx], slice[deleteIdx+1:]...)
fmt.Println(slice)
最後に、copy
関数を使って削除することもできます。
slice = []int{1, 2, 3, 4, 5, 6}
deleteIdx = 2
fmt.Println(slice)
copy(slice[deleteIdx:], slice[deleteIdx+1:])
slice = slice[:len(slice)-1]
fmt.Println(slice)
要素追加
Golangでは次のようにfor
ループを使ってスライスに新しい要素を追加することが出来ます。
slice := []int{1, 2, 3, 4, 5, 6}
insertIdx := 2
fmt.Println(slice)
slice = append(slice, 0)
for i := len(slice) - 2; i >= insertIdx; i-- {
slice[i+1] = slice[i]
}
slice[insertIdx] = 100
fmt.Println(slice)
または、次のようにappend
を使ってスライスに新しい要素を追加することもできます。
slice = []int{1, 2, 3, 4, 5, 6}
insertIdx = 2
fmt.Println(slice)
slice = append(slice[:insertIdx], append([]int{100}, slice[insertIdx:]...)...)
fmt.Println(slice)
最後に、copy
関数を使ってスライスに新しい要素を追加することもできます。
slice = []int{1, 2, 3, 4, 5, 6}
insertIdx = 2
fmt.Println(slice)
slice = append(slice, 0)
copy(slice[insertIdx+1:], slice[insertIdx:])
slice[insertIdx] = 100
fmt.Println(slice)
スライスソーティング
Golangでは基本的提供されるsort
パッケージを使って、スライスをソーティングすることが出来ます。
package main
import (
"fmt"
"sort"
)
func main() {
slice := []int{6, 3, 1, 5, 4, 2}
fmt.Println(slice)
sort.Ints(slice)
fmt.Println(slice)
}
次のように構造体スライスもsort
を使ってソーティングすることが出来ます。
package main
import (
"fmt"
"sort"
)
type Student struct {
Name string
Age int
}
type Students []Student
func (s Students) Len() int { return len(s) }
func (s Students) Less(i, j int) bool { return s[i].Age < s[j].Age }
func (s Students) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
students := []Student{
{"c", 31},
{"a", 20},
{"b", 21},
{"d", 19},
}
fmt.Println(students)
sort.Sort(Students(students))
fmt.Println(students)
}
完了
これでGolangでスライスを宣言して使う方法についてみてみました。また、配列とスライスの違い、スライシングを使って配列からスライスを作る方法も見てみました。
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Deku
が開発したアプリを使ってみてください。Deku
が開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。