Go言語_コンポジット型

配列(Array)

配列は「var 変数名 [配列長]型」で記述します。

配列は宣言時に決定される固定長データであり、途中で長さを変えることはできません。


package main

import "fmt"

func main() {
        var array1 [5]int
        array1[0] = 123
        array1[2] = 456
        array1[4] = 789
        for i, num := range array1 {
                fmt.Printf("array[%d] = %d\n", i, num)
        }
        fmt.Println("")

        array2 := [5]string{"a", "bb", "cc", "ddd", "eee"}
        fmt.Println("array[:]   = ", array2[:])   // [a bb cc ddd eee]
        fmt.Println("array[1:4] = ", array2[1:4]) // [bb cc ddd]
        fmt.Println("array[:4]  = ", array2[:4])  // [a bb cc ddd]
        fmt.Println("array[1:]  = ", array2[1:])  // [bb cc ddd eee]
}

スライス

スライスとは、全ての要素が同じ型である可変長列のデータであり、可変長の配列に類似したデータ構造です。

配列と異なる点は、スライスは要素データを参照型で保持します。


型の宣言

スライスの宣言は、以下のように要素数を記述しない形式です。


var sliceData []int
var sliceData []int{1, 2, 3} // 初期値を定義する。
sliceData := []int{1, 2, 3}  // 変数宣言と初期化。

配列をスライスする

スライスに渡した部分列の要素は元の配列のメモリ領域が共有されます。

スライスの要素を変更すると、参照先の配列データも変更されます。


package main

import "fmt"

func main() {
        // 配列を定義
	arrayData := [4]int{1, 2, 3, 4}

        // スライス型変数の宣言
        var sliceData []int

        // 配列の要素をスライスに代入する。
	sliceData = arrayData[0:3]

        // スライスと配列は同じ先頭要素を指し示しています。
	fmt.Printf("(%p)%v\n", &arrayData, arrayData) // (0x18437000)[1 2 3 4]
	fmt.Printf("(%p)%v\n", sliceData, sliceData)  // (0x18437000)[1 2 3]

        // スライスの要素を変更すると配列のデータも変更されます。
        sliceData[2] = 5
	fmt.Printf("(%p)%v\n", &arrayData, arrayData) // (0x18437000)[1 2 5 4]
}

スライスのデータ構造について

スライスはポインタ(ptr)、長さ(len)、容量(cap)の3つの構成要素を保持しています。

  • ポインタは、スライスを通してアクセス可能な配列要素を指します。
  • 長さは、スライスの要素数です。
  • 容量は、スライスの要素数の最大値です。
    • 配列を参照している場合、参照先の配列の末尾までの要素数となります。
    • append(スライスに要素を追加)した場合に、参照先の配列要素数(容量)よりも大きくなった場合には、参照先を切り替える動作をします。

package main

import "fmt"

func main() {
	arrayData := [4]int{1, 2, 3, 4}
	sliceData := arrayData[0:3]

	// len()でスライスの長さを取得できます。
	fmt.Printf("%v(len=%d)\n", sliceData, len(sliceData)) // [1 2 3](len=3)

	// cap()でスライスの容量を取得できます。
	// 参照先arrayDataの末尾までの要素数となります。
	fmt.Printf("%v(cap=%d)\n", sliceData, cap(sliceData)) // [1 2 3](cap=4)
}

append()関数

スライスに要素を追加するにはappend()関数を用います。

スライスは要素データを参照型で保持しているので、以下のような自動処理が発生します。

  • スライスが既存配列を参照している場合には、参照先を追加された要素で上書きされます。
  • 容量(cap)を超えた要素数を追加した場合、スライスの参照先が自動的に変更されます。
  • 要素追加で参照先が変更された場合、倍の容量のメモリを自動的に確保します。

func main() {
	array := [4]int{1, 2, 3, 4}
	slice1 := array[0:3]

        // スライスに要素を追加する(容量内の要素数を追加)
        slice2 := append(slice1, 5)

        // スライスに要素を追加する(容量より多くの要素を追加)
        slice3 := append(slice2, 6)
}

上記を実行した結果は以下のようになります。

変数名 アドレス 長さ 容量 データ 備考
array 0x18437000 4 4 [1 2 3 5] slice1へのappendで要素値が変更されている
slice1 0x18437000 3 4 [1 2 3] -
slice2 0x18437000 4 4 [1 2 3 5] cap内なので同一のアドレスを指し示す
slice3 0x1842d100 5 8 [1 2 3 5 6] capを超えたので別アドレスに変更される。capが4*2に変更される。

データ共有を切り離したい場合には、copy()関数を利用してスライスの要素をコピーする方法があります。


make()関数

スライスを初期化するには「make(型, 要素数)」と定義します。

スライスが初期化され、デフォルトではゼロ値が格納されます。


package main

import "fmt"

func main() {
        slice := make([]int, 6)
        for i, _ := range slice {
          slice[i] = i
        }

        // (len=6)(cap=6)[0 1 2 3 4 5]
        fmt.Printf("(len=%d)(cap=%d)%v\n", len(slice), cap(slice), slice)
}

make()の第三引数には、容量を指定することもできます。

アドレス参照を変更したくない、ガベージコレクションを行わないといった特殊なケースで用います。


//長さ100の配列を生成し、len=8、cap=100のスライスを返す
slice := make([]int, 8, 100) 

マップ

マップは、他言語における連想配列(ハッシュ、辞書)と同様のデータ構造です。


マップの作成

マップは参照型なので、作成するにはmake関数を使用します。


package main

import "fmt"

func main() {
	// マップの作成
	data := make(map[string]int)

	// key&valueでデータを定義する
	data["one"] = 1
	data["two"] = 2

	// キーを用いてアクセスする
	fmt.Println(data["one"])  // 1

	// キーの存在を確認する
	if val, isExist := data["two"]; isExist {
		fmt.Printf("The key is exists. The value is %#v\n", val)
	}
}

キーの削除

要素の削除にはdelete()関数を用います。


package main

import "fmt"

func main() {
	// マップ宣言時に初期化
	data := map[int]string{
		1: "first",
		2: "second",
		3: "third",
	}

	// 値の更新はキーを指定して代入します。
	data[2] = "This item will be deleted"

	// キーを用いて要素を削除する
	delete(data, 2)
	fmt.Println(data) // map[1:first 3:third]
}

構造体

struct型の定義

構造体型は、structという予約語を使って定義することができます。


package main

import "fmt"

func main() {
	var coordinate struct {
		x float64
		y float64
	}

	coordinate.x = 10.5
	coordinate.y = 7.25

	fmt.Println(coordinate)

}

構造体への命名

type予約語を用いると、既存の型や型リテラルに別名をつけることができます。

構造体ではtypeを用いて定義することが一般的です。


package main

import "fmt"

type coordinate struct {
	x float64
	y float64
}

func main() {
	cd := coordinate{10.5, 7.25}
	fmt.Println(cd)
}

メソッドの追加

構造体にメソッドを定義する場合、レシーバを記述します。

(なお、レシーバーと関連付けた関数をメソッドと言います。)

レシーバは「func (レシーバ名 レシーバの型) 関数名(引数) (戻り値)」という形式です。


package main

import (
	"fmt"
	"math"
)

type coordinate struct {
	x float64
	y float64
}

// ゼロからの距離を取得する
func (cd coordinate) getDistanceFromZero() float64 {
	return math.Pow((0-cd.x)*(0-cd.x)+(0-cd.y)*(0-cd.y), 0.5)
}

func main() {
	cd := coordinate{10.5, 7.25}
	distance := cd.getDistanceFromZero()
	fmt.Println(distance)
}


関連ページ