
Sliceを学んでいきましょう。
前回は、Arrayについて学びました。
Arrayは、大量のデータを扱う時にとても便利なのですが、一つ問題があります。それは、「一度宣言したArrayの要素数を超えて、データを格納することができない」ことです。
この制約は、システム開発する上で、バグを引き起こすトリガーになりえます。
そこで、Sliceの出番です。
Sliceは、Arrayと同じく大量のデータを扱うことを目的に作られたコレクションタイプの構造体で、データの追加機能を備えています。
Sliceは、Go言語の中でも重要なキーワードの一つなので、しっかり学んでいきましょう^^
学習記録
Slices
変数宣言後に、データ追加を可能にするGoのデータ構造体を「Slice(スライス)」と呼びます。
宣言方法は、次の通りです。
1 2 |
// Sliceの宣言 var mySlice []string |
Arrayとは違って、要素数の指定は必要ありません。
1 2 |
// Arrayの宣言 var myArray [5]int |

一見便利そうなSliceですが、使い方に注意が必要です。
Arrayと違ってSliceは、宣言しても自動的にsliceが作成されません。次のサンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
main.go package main import "fmt" func main() { var myArray [5]int var mySlice []int fmt.Println("myArray: ", myArray) fmt.Println("mySLice: ", mySlice) } |
このサンプルコードは、ArrayとSliceを初期値なしで宣言したものです。どのように出力されるでしょうか?
1 2 3 4 |
go run main.go myArray: [0 0 0 0 0] mySLice: [] |
ご覧の通り、Arrayの場合は、宣言されたTypeの初期値でArrayが作成されています。一方、Sliceの場合は、”空”になっていますね。
Sliceを作成するためには、make関数を利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
main.go package main import "fmt" func main() { var myArray [5]int var mySlice []int // int型 領域5 mySlice = make([]int, 5) fmt.Println("myArray: ", myArray) fmt.Println("mySlice: ", mySlice) } |
1 2 3 4 |
$ go run main.go myArray: [0 0 0 0 0] mySlice: [0 0 0 0 0] |
もちろん、SliceにもShort Variable Declarationが使えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
main.go package main import "fmt" func main() { mySlice := make([]int, 5) mySlice[0] = 2 mySlice[1] = 3 fmt.Println("mySlice: ", mySlice) } |
1 2 |
$ go run main.go mySlice: [2 3 0 0 0] |
また、Sliceにもlen関数が使えます。
1 2 3 4 5 6 7 8 9 10 11 |
package main import "fmt" func main() { mySlice := make([]int, 5) mySlice[0] = 2 mySlice[1] = 3 fmt.Println(len(mySlice)) } |
1 2 3 |
$ go run main.go 5 |
もちろん、forやfor…rangeも使えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import "fmt" func main() { numbers := []int{1, 2, 3} for i := 0; i < len(numbers); i++ { fmt.Println(numbers[i]) } for _, v := range numbers { fmt.Println(v) } } |
1 2 3 4 5 6 7 |
$ go run main.go 1 2 3 1 2 3 |
Sliceリテラルを利用すると初期値を設定できます。
1 |
var mySlice = []int {1, 2, 3} |
先ほどの宣言と合わせて、サンプルコードを置いておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "fmt" func main() { numbers := make([]float64, 3) numbers[0] = 19.7 numbers[2] = 25.2 for i, number := range numbers { fmt.Println(i, number) } var letters = []string{"a", "b", "c"} for i, letter := range letters { fmt.Println(i, letter) } } |
1 2 3 4 5 6 7 |
$ go run main.go 0 19.7 1 0 2 25.2 0 a 1 b 2 c |
Sliceについては、大体わかったわ。でも、Sliceを使えば、Arrayは必要なくないかしら?


確かにほとんどの場合は、Sliceを使うと思います。しかし、Arrayを理解しているとSliceの理解が早くなるので、先にArrayを攻略することをお勧めします。
Arrayについては、こちらの記事を参考にしてください。
Sliceオペレータ
Sliceを作成するためには、「make関数」か「Sliceリテラル」を使用する必要がありますが、以下の方法でも作成することが可能です。
1 2 |
// 変数名[開始:終了] mySlice := myArray[1:4] |
サンプルコードでお見せします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
main.go package main import "fmt" func main() { myArray := [5]string{"H", "E", "L", "L", "O"} slice1 := myArray[0:3] fmt.Printf("slice1: %v\n", slice1) slice2 := myArray[0:4] fmt.Printf("slice2: %v\n", slice2) slice3 := myArray[2:] fmt.Printf("slice3: %v\n", slice3) } |
1 2 3 4 5 |
go run main.go slice1: [H E L] slice2: [H E L L] slice3: [L L O] |

気をつけたいポイントは、myArray[i:j]のjですね。指定した数-1の要素まで返却されます。
直感的ではないこの挙動。実は、私が大好きなPythonでも同様です。
1 2 3 4 5 6 7 8 9 |
# pythonのコード >>> my_list = ['H', 'E', 'L', 'L', 'O'] >>> my_list[0:3] ['H', 'E', 'L'] >>> my_list[0:4] ['H', 'E', 'L', 'L'] >>> my_list[2:] ['L', 'L', 'O'] |

ちなみに、要素外にアクセスした場合は、Arrayと同様にエラーになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
main.go package main import "fmt" func main() { myArray := [5]string{"H", "E", "L", "L", "O"} slice1 := myArray[6] fmt.Println(slice1) } |
1 2 3 4 5 |
$ go run main.go # command-line-arguments ./main.go:7:19: invalid array index 6 (out of bounds for 5-element array) |
要素変更の注意点
ArrayやSliceで要素が変更する時に注意点があります。
ひとまず、次のサンプルコードをみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
main.go package main import "fmt" func main() { myArray := [5]string{"H", "E", "L", "L", "O"} mySlice := myArray[0:3] myArray[0] = "F" fmt.Println(myArray) fmt.Println(mySlice) } |
myArrayには、「HELLO」の文字列を格納しましたが、その後、「H」->「F」に書き換えました。
この出力結果は、次のようになります。
1 2 3 |
$ go run main.go [F E L L O] [F E L] |

おやおや、myArrayだけではなく、mySliceに格納した値も変更されています。
このことから、Sliceは、Arrayのデータを参照しているだけであると分かります。
ArrayからSliceを作成できますが、上記のような不思議な動作になるので、バグになりやすいです。
できれば、「make関数」か「slice リテラル」を使いましょう。
Sliceに要素を追加する
Arrayと違って、Sliceには要素を追加できる「append関数」があります。
サンプルコードで挙動を確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import "fmt" func main() { // create a slice slice := []string{"H", "E"} fmt.Println(slice, len(slice)) slice = append(slice, "L") fmt.Println(slice, len(slice)) slice = append(slice, "L", "O") fmt.Println(slice, len(slice)) } |
実行してみましょう。
1 2 3 4 5 6 |
$ go run main.go [H E] 2 [H E L] 3 [H E L L O] 5 |
append関数の第一引数にSliceを指定し、第二引数以降に追加したい要素を順番に設定していくだけです(簡単ですよね)。
しかし、append関数はバグを生み出しやすいです。次のサンプルをみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import "fmt" func main() { joy := []string{"joy", "joy"} mery := append(joy, "mery", "mery") sara := append(mery, "sara", "sara") gen := append(sara, "gen", "gen") fmt.Println(joy, mery, sara, gen) gen[0] = "TAGUCHI" fmt.Println(joy, mery, sara, gen) } |
joyくん、meryさん、 saraさん、genくんのスライスを用意しました。
その後、genくんの一番目の要素には、TAGUCHIくんを格納しています。
出力結果はどうなると思いますか?
試してみましょう。
1 2 3 4 5 |
$ go run main.go [joy joy] [joy joy mery mery] [joy joy mery mery sara sara] [joy joy mery mery sara sara gen gen] [joy joy] [joy joy mery mery] [TAGUCHI joy mery mery sara sara] [TAGUCHI joy mery mery sara sara gen gen] |
genくんの一番目の要素にTAGUCHIくんを入力しました。その影響が、saraさんの一番目の要素にも現れています。
これは、saraさんとgenくんが同じ戻り値の情報を元に作成されたものだからです。その証拠にmeryさんとjoyくんは変更が共有されません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import "fmt" func main() { joy := []string{"joy", "joy"} mery := append(joy, "mery", "mery") sara := append(mery, "sara", "sara") gen := append(sara, "gen", "gen") fmt.Println(joy, mery, sara, gen) gen[0] = "TAGUCHI" fmt.Println(joy, mery, sara, gen) mery[0] = "Harry" fmt.Println(joy, mery, sara, gen) } |
1 2 3 4 5 6 |
$ go run main.go [joy joy] [joy joy mery mery] [joy joy mery mery sara sara] [joy joy mery mery sara sara gen gen] [joy joy] [joy joy mery mery] [TAGUCHI joy mery mery sara sara] [TAGUCHI joy mery mery sara sara gen gen] [joy joy] [Harry joy mery mery] [TAGUCHI joy mery mery sara sara] [TAGUCHI joy mery mery sara sara gen gen] |
上記の挙動から参照情報は上書きされていくと思われます。
meryさんの一番目の要素が変更されたら、meryさんを元に作られたsaraさんの要素にも影響がありそうですが、実際は影響なしだからです。
この挙動は、本当にわかりずらいですよね?
バグを回避するためには、append関数の戻り値は、「同じ変数名」で受け取ることが大切です。
1 2 3 4 5 6 7 8 9 10 11 12 |
package main import "fmt" func main() { slice := []string{"s1", "s1"} slice = append(slice, "s2", "s2") slice = append(slice, "s3", "s3") slice = append(slice, "s4", "s4") fmt.Println(slice) } |
1 2 3 |
$ go run main.go [s1 s1 s2 s2 s3 s3 s4 s4] |
Sliceの初期値
Sliceの初期値には、指定したtypeの初期値が指定されます。
サンプルコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 |
package main import "fmt" func main() { fSlice := make([]float64, 10) bSlice := make([]bool, 10) sSlice := make([]string, 10) iSlice := make([]int, 10) fmt.Println(fSlice[0], bSlice[0], sSlice[0], iSlice[0]) } |
1 2 3 4 |
$ go run main.go 0 false 0 |
これは、Arrayと同じ性質です。しかし、Arrayと違って、slice変数自身は、nilを持ちます。
1 2 3 4 5 6 7 8 9 10 11 |
package main import "fmt" func main() { var fSlice []float64 var bSlice []bool var sSlice []string var iSlice []int fmt.Printf("fSlice: %#v, bSlice:%#v, sSlice:%#v, iSlice:%#v", fSlice, bSlice, sSlice, iSlice) } |
1 2 |
$ go run main.go fSlice: []float64(nil), bSlice:[]bool(nil), sSlice:[]string(nil), iSlice:[]int(nil)$ |
この状態の場合、len関数を用いて要素数を出力すると0が出力されます。
1 2 3 4 5 6 7 8 |
package main import "fmt" func main() { var sSlice []string fmt.Println(len(sSlice)) } |
1 2 3 |
$ go run main.go 0 |
よって、条件判定を行いたい場合は、次のようにすれば良いでしょう。
1 2 3 4 5 6 7 8 9 10 11 |
package main import "fmt" func main() { var sSlice []string if len(sSlice) == 0 { sSlice = append(sSlice, "Append Item") } fmt.Printf("%#v\n", sSlice) } |
1 2 3 |
$ go run main.go []string{"Append Item"} |
やってみよう1

久々のやってみようのコーナーです。
プログラミング学習は、アウトプットが大切です。
次のプログラムを作ってみましょう。
・dataファイルを読み込む
・dataファイルには、整数が格納されている
・dataファイルには、整数以外の値が含まれている場合がある
・読み込んだ変数の平均値を求める

簡単なプログラムですが、調べないとわからないと思います。余力がある人は、独力でやってみてください。
まずは、作業フォルダとファイルを作成します。
1 2 3 4 5 6 7 8 |
mkdir work cd work mkdir average touch average/main.go touch data.txt mkdir datafile touch datafile/ints.go |
data.txtの中身は次の通りです。
1 2 3 4 5 6 |
data.txt 10 20 30 四十 |
data/ints.goには、data.txtを受け取って中身を取り出す処理を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// package datafileは、dataファイルを読み込む package datafile import ( "bufio" "fmt" "os" "strconv" ) // GetInts は、int型のスライスを返却する func GetInts(fileName string) ([]int, error) { var numbers []int f, err := os.Open(fileName) if err != nil { return numbers, err } // ファイルからデータを読み込む scanner := bufio.NewScanner(f) for scanner.Scan() { // 数値に変換 number, err := strconv.Atoi(scanner.Text()) //数値変換に失敗した場合 if err == nil { numbers = append(numbers, number) } else { fmt.Println(err) } } return numbers, nil } |
呼び出し元から「data.txt」を受け取ったら中身を取り出して、数値の場合はint型のスライスに格納します。
strconv.Atoi関数で文字列->数値に変換しており、数値に変換できない場合は、エラーが発生します。
続いて、average/main.goを実装しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
average/main.go // average calculates the average of serveral numbers package main import ( "fmt" "log" "github.com/hoge/headfirstgo/datafile" ) func main() { numbers, err := datafile.GetInts("data.txt") if err != nil { log.Fatal(err) } var sum int = 0 for _, number := range numbers { sum += number } sampleCount := len(numbers) fmt.Printf("Average: %0.2d\n", sum/sampleCount) } |
averageフォルダ内で、「go run main.go」を実行してみましょう。
1 2 3 4 |
go run main.go strconv.Atoi: parsing "四十": invalid syntax Average: 20 |
問題なさそうですね。文字列(四十)もエラー処理できているようです。
コマンドライン引数
Sliceのついでにコマンドライン引数についても軽く触れておきましょう。
下記のフォルダとファイルを作成してください。
1 2 3 |
mkdir average cd average touch main.go |
コマンドラインからの入力を受け取りたい場合は、「os.Args」関数を用います。
1 2 3 4 5 6 7 8 9 10 11 |
package main import ( "fmt" "os" ) func main() { fmt.Println(os.Args) fmt.Println(os.Args[1:]) } |
terminal上で実行してみましょう。
1 2 3 4 |
$ go run main.go 1 2 3 4 5 [/var/folders/zx/vr9x_3l51g3d02wd9dl7_vkw0000gn/T/go-build865697185/b001/exe/main 1 2 3 4 5] [1 2 3 4 5] |
このように簡単にユーザーの入力項目が取得できました。
os.Argsは、コマンドラインの引数を「String typeのSlice」として受け取ります。
やってみよう2
やってみよう1では、既存のファイルから値を読み取り、平均値を算出しました。今度は、コマンドライン引数から平均値を算出してみましょう。
サンプルを載せておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package main import ( "fmt" "log" "os" "strconv" ) func main() { // 引数を受け取る args := os.Args[1:] var sum int = 0 // for ...rangeでループ for _, arg := range args { // string -> intに変換 num, err := strconv.Atoi(arg) if err != nil { log.Fatal(err) } sum += num } // 引数の数を取得 sampleCount := int(len(args)) fmt.Printf("Average: %v\n", sum/sampleCount) } |
1 2 |
$ go run main.go 1 2 3 4 5 Average: 3 |
可変長引数
可変長引数について少しだけ触れておきます。
次のサンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import ( "fmt" ) func main() { fmt.Println(1) fmt.Println(1, 2, 3, 4, 5, 6) l := []string{"a"} l = append(l, "b", "c") l = append(l, "d", "e") fmt.Println(l) } |
これを実行します。
1 2 3 4 |
$ go run main.go 1 1 2 3 4 5 6 [a b c d e] |
予想通りの出力結果が出ましたね。
では、次の関数を実行するとどうなるでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import ( "fmt" ) func argsTest(first int, second int) { fmt.Println(first, second) } func main() { argsTest(1, 2, 3, 4, 5) } |
ざっくり説明すると2つの引数を受け取るargsTest関数に、”複数個“の引数を渡しています。
これは当然コンパイルエラーになります。
1 2 3 4 5 |
$ go run main.go # command-line-arguments ./main.go:12:10: too many arguments in call to argsTest have (number, number, number, number, number) want (int, int) |
最初のサンプルの実行では、”複数の引数を指定した実行は可能“でした。この違いは何なのでしょうか?
実は、これは「可変長引数」と呼ばれる実装によるものです。
可変長引数は、「呼び出し側の引数がいくら増えても呼び出し先で受け止めてくれる」便利な引数です。
次のように実装します。
1 2 3 |
func argsTest(param1 int, param2 ...string) { // function process... } |
最後のパラメータのtypeに「…」を付与することで、値をスライスで受け取れるようになります。
では、先ほどの関数を可変長引数バージョンに書き直して見ましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import ( "fmt" ) func argsTest(params ...int) { fmt.Println(params) } func main() { argsTest(1, 2, 3, 4, 5) } |
実行して見ましょう。
1 2 3 |
$ go run main.go [1 2 3 4 5] |
可変長引数で受け取った値が、スライスで取得できていることがわかると思います。
ちなみに、引数を渡さないで実行した場合は、空のスライスが返却されます。
やってみよう3
やってみよう2のサンプルコードを可変長引数版に書き直してみましょう。
for … rangeで数値を合計していた箇所を別関数に切り離し、そこに可変長引数をセットする感じで実装してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package main import ( "fmt" "log" "os" "strconv" ) func addNumbers(params ...string) int { var sum int = 0 // for ...rangeでループ for _, param := range params { // string -> intに変換 num, err := strconv.Atoi(param) if err != nil { log.Fatal(err) } sum += num } return sum } func main() { // 引数を受け取る params := os.Args[1:] sum := addNumbers(params...) // 引数の数を取得 count := int(len(params)) fmt.Printf("Average: %v\n", sum/count) } |
恐らく、可変長引数へのスライスの渡し方に若干手こずると思います。
os.Argsから受け取った値は、スライスで変数に格納されます。これを可変長引数に渡したい場合は、「変数名…」のように省略記号を用いる必要があります。
上記の例では、「sum := addNumbers(params…)」がそれに該当します。
プログラムを実行してみましょう。
1 2 |
$ go run main.go 10 20 30 Average: 20 |
問題なくプログラムが動いていますね。
まとめ

最後に本章で学習したことをまとめて終わりにしましょう。
・ スライスの宣言方法 -> var mySlice []int
・ Arrayと同様にインデックスで値にアクセスする
・ Arrayと同様にlen関数やfor…rangeループ処理が可能
・ Sliceを作成するには、make関数かSliceリテラルが有効
・ Sliceリテラルを使うと初期化&値格納が同時にできる -> []int{1, 2, 3}
・ sliceオペレーターが使える -> s[i:j]など
・ os.Argsを使うとコマンドライン引数をスライスで受け取れる
・ 可変長引数を設定した関数は、呼び出し元から何個でも引数を受け取れる
・ 可変長引数を設定するには、最後のパラメーターに「…」を設定する
・ 可変長引数は、スライスで受け取る
・ 呼び出し元がスライスを渡す場合は、省略記号を用いる->mySlice…
参考書籍
次回
次回は、Mapについて学んでいきましょう。
コメントを残す
コメントを投稿するにはログインしてください。