blog

Go初心者シリーズの配列とスライス (3)

1. 配列 配列は、同じ型の複数の変数のコレクションを格納するために使用されます。配列内の各変数は配列の要素と呼ばれ、各要素には 0 から始まる数値(配列添え字)があり、個々の要素を区別するために使用...

Mar 4, 2020 · 9 min. read
シェア

Go言語を始めるための連載記事

配列

配列は、同じ型の変数の集まりを複数格納するために使用します。配列内の各変数は配列の要素と呼ばれ、各要素には 0 から始まる数値(配列添え字)が与えられ、個々の要素を区別するために使用されます。配列に格納できる要素の数を、配列の長さと呼びます。

宣言

Go言語における配列の宣言方法:

var arr_name [length]type

var: 言うまでもなく、このキーワードは変数を宣言するときに使います。

arr_name:配列の名前、実質的には変数

length: 配列の長さ

type: 配列の型

[配列要素の読み出しと代入を行います。

以下はその例です:

package main
import "fmt"
func main() {
 var a [2]string //長さ2の文字列配列を宣言する。
 a[0] = ""	 // 
 a[1] = "ラインミニビュー"
 fmt.Println(a[0], a[1]) //要素の取得
 fmt.Println(a)
}

初期化

囲碁の基本構文」で述べたように、初期値なしで変数を宣言した場合、その変数には「ゼロ値」が与えられます。

配列も同様で、初期化されていなければ、配列の要素はすべて「ゼロ値」になります。以下はその例です:

package main
import "fmt"
func main() {
 var a [3]int
 var b [3]string
 var c [3]bool
 fmt.Println(a) //[0 0 0]
 fmt.Println(b) //[ ]
 fmt.Println(c) //[false false false]
}

配列要素を初期化します:

package main
import "fmt"
func main() {
 var a = [5]int {1, 2, 3}
 fmt.Println(a) //[1 2 3 0 0]
}

初期化されるのは一部の要素だけで、残りはゼロ値のままです。

配列が宣言と同時に初期化される場合は、... を使用できます。配列の長さを指定しないと、Go は自動的に配列の長さを計算します:

var a = [...]int {1, 2, 3} //最初は、配列の長さは3である。

短い変数ウェイ宣言

もちろん、配列は短い変数宣言を使って宣言することもできます。注意:この方法を使用するには、宣言と同時に初期化を行う必要があります

この方法で配列を初期化せずに宣言するだけなら可能ですが、{}も一緒に持っていかなければなりません。

package main
import "fmt"
func main() {
	a := [5]int {1, 2, 3} // 
 b := [3]int {}
 c := [...]int {1, 2, 3}
	fmt.Println(a) //[1 2 3 0 0]
 	fmt.Println(b) //[0 0 0]
 fmt.Println(c) //[1 2 3]
}

特別な機能

注意:Goでは、配列の長さはその型の一部です。 そのため、Goの配列はその長さを変更できません。

どのように理解するのですか?以下に2つの配列を宣言します:

var a [4]int //変数aを4つの整数の配列として宣言する。
var b [5]int //変数bを5つの整数の配列として宣言する。

変数aとbはそれぞれ異なる型[4]intと[5]intです。

二次元配列

二次元配列の要素は、やはり配列です:

var ab = [2][4]int {[4]int {1, 2, 3, 4}, [4]int {4, 5, 6, 7}}
 
ab := [2][4]int {[4]int {1, 2, 3, 4}, [4]int {4, 5, 6, 7}}

配列要素の型は省略可能:

var ab = [2][4]int {{1, 2, 3, 4}, {4, 5, 6, 7}}
 
ab := [2][4]int {{1, 2, 3, 4}, {4, 5, 6, 7}}

配列の走査

配列の長さの使用

len(slice)関数で配列の長さを取得し、それを繰り返し処理します。

arr := [5]string {"a", "b", "c", "d", "e"}
bc := [2][4]int {
 {1, 2, 3, 4}, 
 {5, 6, 7, 8},
}
for i := 0; i < len(arr); i++ {//一次元配列を走査する
 fmt.Println(arr[i])
}
for i := 0; i < len(bc); i++ {//2次元配列の要素を反復処理する
 fmt.Println(bc[i])
}
for i := 0; i < len(bc); i++ {//2次元配列の要素を反復処理する。
 for j := 0; j < len(bc[0]); j++ {
 fmt.Println(bc[i][j])
 }
}

rangeキーワードの使用

range キーワードは、for ループで配列を走査するときに使用します。各反復は 2 つの値を返し、最初の値は現在の要素の添え字で、2 番目の値はその添え字に対応する要素の値です。この 2 つの値のいずれかが不要な場合は、代わりにアンダースコア _ を使用します。

arr := [5]string {"a", "b", "c", "d", "e"}
bc := [2][4]int {
 {1, 2, 3, 4},
 {5, 6, 7, 8},
}
for i, v := range arr {//一次元配列を走査する
 fmt.Println(i, v)
}
for i := range arr {//トラバースは添え字
 fmt.Println(i)
}
for _, v := range arr{//トラバースは要素の値のみを取得する
 fmt.Println(v)
}
for _, v := range bc {//2次元配列をトラバースする
 for _, w := range v{
 fmt.Println(w)
 }
}

スライス

前述したように、Goの配列は固定長です。というのも、配列を宣言する前に、その配列に格納する要素の数がはっきりしないことがよくあるからです。宣言する要素の数が多すぎると無駄になりますし、少ないと足りなくなります。

スライスは「ダイナミック・アレイ」を提供します。

スライスの宣言は配列の宣言と似ていますが、長さを指定しません

var sli_name []type

例えば、int型のスライスをaという名前で宣言します:

var a []int

宣言時に直接初期化できます:

var a = []int {1, 2, 3, 4}

もちろん、ショート変数を使って宣言することもできます:

a := []int {1, 2, 3, 4}

スライスは、既存のアレイまたは既存のスライスから取得できます。

スライスを取得する方法は、コロンで区切られた 2 つの添え字、開始添え字と終了添え字です。startIndex を含み、endIndex を除きます:

a[startIndex : endIndex]

以下はその例です:

a := [5]string {"a", "b", "c", "d", "e"} // 
b := []int {1, 2, 3, 4} // 
sliA := a[2:4]
sliB := b[1:3]
fmt.Println(sliA) //[c d]
fmt.Println(sliB) //[2 3]

スライスと配列

前述したように、スライスは「動的配列」を提供します。しかし、"動的配列 "は実際には長さを拡張できる動的配列ではありません。

スライスはいかなるデータも格納せず、単なる参照型であり、スライスは常に、基礎となる配列のセグメントを記述する基礎となる配列を指します。

そのため、配列を宣言するときには長さを指定する必要がありますが、スライスを宣言するときには長さを指定する必要はありません:

var arr = [4]int {1, 2, 3, 4} //配列を宣言する
var slice = []int {1, 2, 3, 4} //スライスの宣言

スライスの基礎となる参照は配列であるため、スライスの要素を変更すると、その基礎となる配列の対応する要素が変更され、その基礎となる配列を参照する他のスライスが存在する場合、それらのスライスも変更を観察することができます。図に示すように

以下はその例です:

package main
import "fmt"
func main() {
	array := [5]string {"aa", "bb", "cc", "dd", "ee"} // 
	fmt.Println(array) //[aa bb cc dd ee]
	slice1 := array[0:2] // 
	slice2 := array[1:3] // 
	slice3 := array[2:5] // 
	fmt.Println(slice1) //[aa bb]
	fmt.Println(slice2) //[bb cc]
	fmt.Println(slice3) //[cc dd ee]
	slice1[0] = "xx" //スライス1の値を変更するには
	slice2[1] = "yy" //スライス2の値を変更するには
	slice3[2] = "zz" ////スライス3の値を変更するには
	fmt.Println(array) //[xx bb yy dd zz]
	fmt.Println(slice1) //[xx bb]
	fmt.Println(slice2) //[bb yy]
	fmt.Println(slice3) //[yy dd zz]
}

スライスの関連操作

長さ

スライスの長さとは、そのスライスが含む要素の数を指します。スライス s の長さは関数 len(s) で求められます。

計量

スライスの容量は、スライスの最初の要素から、その基礎となる配列の最後の要素までの要素数です。スライス s の容量は関数 cap(s) で求められます。

以下はその例です:

package main
import "fmt"
func main() {
	arr := [10]string {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
	s := arr[2:5] //スライスsを作成するには
	fmt.Println(arr) //[a b c d e f g h i j]
	fmt.Println(s) //[c d e]
	fmt.Println(len(s)) //3
	fmt.Println(cap(s)) //8
}

以下は長さと容量の概略図です:

容量という概念があれば、スライスを切り直すことで長さを変えることができます:

package main
import "fmt"
func main() {
	arr := [10]string {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
	s := arr[2:5]
	fmt.Printf("sスライスの長さは%d,sの容量は%d
", len(s), cap(s))
	s = s[2:8]
	fmt.Printf("sスライスの長さは%d,sの容量は%d
", len(s), cap(s))
	s = s[0:2]
	fmt.Printf("sスライスの長さは%d,sの容量は%d
", len(s), cap(s))
}

付加要素

func append(slice []Type, elems ...Type) []TypeType型の要素エレメントをスライス・スライスの末尾に追加するために使用します。

この関数の結果は、元のスライスのすべての要素と新しく追加された要素を含むスライスです。スライスの内容が変更されるため、基礎となる配列も変更されます。

package main
import "fmt"
func main() {
	s := []string {"a", "b", "c", "d"}
	s = append(s, "e") //1 を追加する
	s = append(s, "f", "g", "h") //を追加する。
	fmt.Println(s)
}

スライスの容量を使い果たしたとき、つまり基礎となる配列が追加の要素を保持できなくなったとき、Goはより大きな基礎となる配列を確保し、返されるスライスはこの新しく確保された配列を指し、元の配列の内容は変更されません。

package main
import "fmt"
func main() {
	arr := [5]string {"a", "b", "c", "d", "e"}
	slice1 := arr[0:2]
	fmt.Println(slice1) //[a b]
	//3つの要素を追加する。
	slice1 = append(slice1, "1", "2", "3")
	fmt.Println(slice1) //[a b 1 2 3]
 //基礎となる配列は
	fmt.Println(arr) //[a b 1 2 3]
	//を追加し続ける。
	slice1 = append(slice1, "4", "5")
	//新しい配列を指す
	fmt.Println(slice1) //[a b 1 2 3 4 5]
	//元の配列は変更されない
	fmt.Println(arr) //[a b 1 2 3]
}

重複スライス

func copy(dst []Type, src []Type) int

この関数はsrcの要素をdstにコピーし、コピーされた要素の数を返します。

package main
import "fmt"
func main() {
	slice1 := []string {"a", "b"}
	slice2 := []string {"1", "2", "3"}
	length := copy(slice2, slice1)
	//length := copy(slice1, slice2)
	fmt.Println(length)
	fmt.Println(slice1)
	fmt.Println(slice2)
}

スライスのデフォルト動作

スライスの既定の開始添え字は 0 で、既定の終了添え字はスライスの長さです。

アレイの場合:

var a [10]int

以下のスライスは同等です:

a[0:10]
a[:10]
a[0:]
a[:]

特殊なスライス

ゼロスライス

スライスのゼロ値はnilで、スライスが宣言されたが初期化されていない場合、そのスライスはnilスライスです。nilスライスは長さと容量が0で、基礎となる配列はありません。

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("sスライスはnilスライスである。")
	}
}

スライス用スライス

スライス内の要素はスライスすることができます。

package main
import "fmt"
func main() {
	ss := [][]int {
		[]int {1, 2, 3}, //スライス要素の型は省略可能である。
		[]int {4, 5, 6},
		[]int {7, 8, 9},
	}
	for i := 0; i < len(ss); i++ {
		fmt.Println(ss[i])
	}
}

make関数によるスライスの作成

make関数を使用して、スライス作成時にスライスの長さと容量を指定します。make関数は0値の要素の配列を確保し、それを参照するスライスを返します。

この関数は、スライスのタイプ、長さ、容量を指定する3つの引数を受け付けます。容量パラメータが渡されない場合、容量は長さと同じになるのがデフォルトです。capacityパラメータはlengthパラメータより小さくすることはできません。

package main
import "fmt"
func main() {
	a := make([]int, 5)
	fmt.Println(a, len(a), cap(a)) //[0 0 0 0 0] 5 5
	b := make([]int, 5, 6)
	fmt.Println(b, len(b), cap(b)) //[0 0 0 0 0] 5 6
	//c := make([]int, 5, 4) 
	//fmt.Println(c, len(c), cap(c))//エラーが報告された: make() で len が cap より大きい。[]int)
}

スライスの走査

スライスは配列への参照なので、スライスをトラバースすることは配列をトラバースすることでもあります。

Read next

[準備] JAVAの基礎

シリアライズとは、オブジェクトをバイト列に変換することです。 テキスト、画像、音声、動画などのネットワーク伝送の場合

Mar 3, 2020 · 5 min read