Go 速習チュートリアル

Go 言語のスライス操作

1. スライスの要素へのアクセス

スライスの特定のエレメント(要素)にアクセスするには、インデックス(Index)番号を参照します。

Goでは、インデックスは 0 から始まります。つまり、[0] は最初のエレメント、[1] は2番目のエレメントを指します。

実装例:
この例では、prices スライスの最初と3番目のエレメントにアクセスする方法を示しています。

package main
import ("fmt")

func main() {
  prices := []int{10, 20, 30}

  fmt.Println(prices[0])
  fmt.Println(prices[2])
}

実行結果:

10
30

2. スライスの要素の変更

インデックス番号を参照することで、特定のスライスエレメントの値を書き換えることも可能です。

実装例:
この例では、prices スライスの3番目のエレメントを変更する方法を示しています。

package main
import ("fmt")

func main() {
  prices := []int{10, 20, 30}
  prices[2] = 50
  fmt.Println(prices[0])
  fmt.Println(prices[2])
}

実行結果:

10
50

3. スライスへの要素の追加

append() ファンクション(関数)を使用すると、スライスの末尾に新しいエレメントを追加できます。

構文(Syntax)

slice_name = append(slice_name, element1, element2, ...)

実装例:
スライスの末尾にエレメントを追加する例です。

package main
import ("fmt")

func main() {
  myslice1 := []int{1, 2, 3, 4, 5, 6}
  fmt.Printf("myslice1 = %v\n", myslice1)
  fmt.Printf("レングス = %d\n", len(myslice1))
  fmt.Printf("キャパシティ = %d\n", cap(myslice1))

  myslice1 = append(myslice1, 20, 21)
  fmt.Printf("追加後のmyslice1 = %v\n", myslice1)
  fmt.Printf("追加後のレングス = %d\n", len(myslice1))
  fmt.Printf("追加後のキャパシティ = %d\n", cap(myslice1))
}

実行結果:

myslice1 = [1 2 3 4 5 6]
レングス = 6
キャパシティ = 6
追加後のmyslice1 = [1 2 3 4 5 6 20 21]
追加後のレングス = 8
追加後のキャパシティ = 12

4. スライス同士の結合

あるスライスのすべてのエレメントを別のスライスに追加する場合も、append() ファンクションを使用します。

構文(Syntax)

slice3 = append(slice1, slice2...)

       注意: スライス同士を結合する場合、2つ目のスライスの後ろに ...(ドット3つ)を付ける必要があります。これはスライスのエレメントを展開して渡すための記法です。

実装例:

package main
import ("fmt")

func main() {
  myslice1 := []int{1, 2, 3}
  myslice2 := []int{4, 5, 6}
  myslice3 := append(myslice1, myslice2...)

  fmt.Printf("myslice3 = %v\n", myslice3)
  fmt.Printf("レングス = %d\n", len(myslice3))
  fmt.Printf("キャパシティ = %d\n", cap(myslice3))
}

5. スライスのレングスの変更

アレイ(配列)とは異なり、スライスは作成後もレングス(長さ)を変更することが可能です。

実装例:
再スライシングや追加操作によってレングスを変更する例です。

package main
import ("fmt")

func main() {
  arr1 := [6]int{9, 10, 11, 12, 13, 14} // アレイ
  myslice1 := arr1[1:5] // アレイからスライスを作成
  fmt.Printf("myslice1 = %v\n", myslice1)
  fmt.Printf("レングス = %d\n", len(myslice1))
  fmt.Printf("キャパシティ = %d\n", cap(myslice1))

  myslice1 = arr1[1:3] // 再スライシングによるレングス変更
  fmt.Printf("再スライシング後 = %v\n", myslice1)
  fmt.Printf("レングス = %d\n", len(myslice1))
  fmt.Printf("キャパシティ = %d\n", cap(myslice1))

  myslice1 = append(myslice1, 20, 21, 22, 23) // 追加によるレングス変更
  fmt.Printf("追加後 = %v\n", myslice1)
  fmt.Printf("レングス = %d\n", len(myslice1))
  fmt.Printf("キャパシティ = %d\n", cap(myslice1))
}

6. メモリ効率の最適化

スライスを使用する際、Goはすべての「基底配列(Underlying array)」のエレメントをメモリに保持し続けます。

もし巨大なアレイがあり、その中のほんの数エレメントしか必要ない場合、スライシングだけでは元の巨大なアレイがメモリに残ってしまいます。これを避けるには、copy() ファンクションを使用して必要なエレメントだけを新しいスライスにコピーするのがベストプラクティスです。

copy() ファンクションは、必要なエレメントだけを保持する新しい基底配列を作成します。これにより、プログラムのメモリ消費量を抑えることができます。

構文(Syntax)

copy(dest, src)

copy()dest(コピー先)と src(コピー元)の2つのスライスを受け取り、src から dest へデータをコピーします。戻り値としてコピーされたエレメント数を返します。

実装例:

package main
import ("fmt")

func main() {
  numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
  // オリジナルのスライス
  fmt.Printf("numbers = %v\n", numbers)
  fmt.Printf("レングス = %d\n", len(numbers))
  fmt.Printf("キャパシティ = %d\n", cap(numbers))

  // 必要な数値だけを抽出してコピー
  neededNumbers := numbers[:len(numbers)-10]
  numbersCopy := make([]int, len(neededNumbers))
  copy(numbersCopy, neededNumbers)

  fmt.Printf("numbersCopy = %v\n", numbersCopy)
  fmt.Printf("コピー後のレングス = %d\n", len(numbersCopy))
  fmt.Printf("コピー後のキャパシティ = %d\n", cap(numbersCopy))
}

解説:
新しいスライスのキャパシティは、オリジナルのスライスよりも小さくなっています。これは、新しい基底配列が最小限のサイズで作成されたためです。

スライスの柔軟性は非常に強力ですが、この「基底配列との関係」を意識することで、よりリソース効率の良いGoプログラムを書くことができます。メモリ使用量がシビアなサーバーサイド開発では、特に copy() の使いどころが重要になってきます。