こんにちは。scarvizです。
今回はスライスのappendについて注意したい点を取り上げます。
■スライスのappendの動き
初期化済みのスライスに新たに要素を追加したい、スライスとスライスを連結させたい、という場合があると思います。
そういう時にappendを使用します。
以下のコードで、最初に出力されるのは要素の追加、次に出力されるのはスライスの連結です。
スライスにスライスを連結させる場合、連結させるスライスの後に「...」をつけることを忘れないようにしてください。
package main import "fmt" func main() { // スライス初期化 intSlice := []int{1, 2, 3} // 新たに"4"という値の要素を加える intSlice = append(intSlice, 4) fmt.Println(intSlice) intSlice2 := []int{5, 6, 7} // スライスとスライスを連結する intSlice = append(intSlice, intSlice2...) fmt.Println(intSlice) }
結果:
[1 2 3 4] [1 2 3 4 5 6 7]
■ループ中にスライスにappendさせる
ループ処理の中で算出される値を、スライスに追加していくような処理を書きたいという場合があると思います。
まずは以下のコードをご覧ください。
package main import "fmt" func main() { // 長さ(キャパシティ)が10のスライス intSlice := make([]int, 10) // ループで0~9の値の要素を順番に追加 for i := 0; i < 10; i++ { intSlice = append(intSlice, i) } fmt.Println(intSlice) }
このスライスは長さ(キャパシティ)が10あり、10個int型の値を追加しようとしているものです。
これを実行すると結果は以下になります。
結果:
[0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
これは全然期待していたものとは違うと思います。
■原因
スライスの初期化では初期値として、その型のゼロ値が格納されます。今回の場合だとint型のゼロ値(0)が10個入った状態になります。
そのため、このスライスにappendすると、0が10個の後に0~9が追加される事になります。
先ほどのコードの初期化後の状態を出力して確認してみます。
package main import "fmt" func main() { // 長さ(キャパシティ)が10のスライス intSlice := make([]int, 10) // スライスの初期状態 fmt.Println(intSlice) // ループで0~9の値の要素を順番に追加 for i := 0; i < 10; i++ { intSlice = append(intSlice, i) } fmt.Println(intSlice) }
結果:
[0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
0が10個入ってますね。
■対策
このゼロ値を回避するには、以下のようにすれば良いです。
package main import "fmt" func main() { // 初期化時に長さ(キャパシティ)を0とする intSlice := make([]int, 0) for i := 0; i < 10; i++ { intSlice = append(intSlice, i) } fmt.Println(intSlice) }
結果:
[0 1 2 3 4 5 6 7 8 9]
期待している結果になりましたね。
ちなみにスライスを初期化しない(nilの状態)ではappendできません。
気付けば大したことは無いのですが、よくありがちなミス(少なくとも僕はやっちゃってました)なので、気をつけてくださいね。
append(slice1, slice2...) の"..."は
返信削除正確には何の意味があるんでしょうか?
気になります!
僕も気になったので、ちょっと調べてみました。
削除結論としては、ちゃんと分かりませんでした・・・。
調査した結果だけ。
実処理ではなく、ドキュメント用に用意されている
$GOROOT/src/pkg/builtin/builtin.go
では、appendは、
func append(slice []Type, elems ...Type) []Type
と定義されています。
elemsは可変長パラメータになっているため、要素を渡すと、その要素のスライスを作成するそうです。
ここで追加要素ではなくスライスを足したい時に、もし"..."という記号(?)がなければ、
スライスのスライスを作成することになります。
なので、足し合わせるスライスには明示的に"..."を付けているのではないかなと思います。
ただ、追加要素なのかスライス自体なのかは、スライスの場合に"..."を付けないとエラーになるので、
見分けれるんじゃないかなとも思うので、実際のところは分かりません。
分かる方いたら教えてください!
大きさが分かっている場合は、intSlice := make([]int, 0,10)のようにして、キャパシティ(長さではない)を指定してやるといいようですよ。appendした際にバックにある配列が再生成されないようです。
返信削除http://golang.org/pkg/builtin/#append
ありがとうございます!
削除なるほど。最初説明を読んだときは、引数に渡したスライスが追加されて返って来るのかと思ってましたが、内部的に再作成するかどうかを判断してるということなんですね。
そもそも、スライスを引数で渡しても参照渡しではないので、それに追加されて返ってくる事は無いですね。
http://golang.org/ref/spec#Calls
返信削除にちゃんと説明ありました。
...Type: でType指定してますから
なんでもかんでも渡す、というものではないようなので、
コンパイル時にちゃんとちぇっく入りそうですね。
append固有の話は調べてませんが...
英語半分しか読んでない(読めてない)ですが、それっぽい事書いてますね。
削除appendの定義も同じなので、というか、"..."についてはどれも同じだと思うので、理由としては合ってそうですね。
見分けてくれたら楽なんですけど(笑)