Go言語マスター 〜ポインタ・構造体・スライス〜

公開日:2022-09-18
go

https://youtu.be/YHzKLJMghqY

はじめに

本記事では、Go言語でのポインタ・構造体・スライスの記述方法を説明します。

1.Pointers

Go言語には、C言語やC++にあるようなポインタを扱うことができます。
ポインタとは、メモリ上にあるデータのアドレスを返してくるモノです。
使用例としては、ファイルの構造体などコピーされたくないデータをポインタで扱う場合があります。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    i, j := 42, 2701

    p := &i         
    fmt.Println(*p) //42
    *p = 21         
    fmt.Println(i)  //21

    p = &j         
    *p = *p / 37   
    fmt.Println(j)  //73
}

まず、ポインタの変数を宣言する方法は次のようです。

var p *int

つぎは、既存の変数のポインタを引き出す方法は次のようです。

i := 42
p = &i

変数iのポインタを引き出す場合は&iを使用します。

つぎは、変数のアドレスから、変数の値を引き出す方法は次のようです。

*p = 21

このように変数にアスタリスクをつけると変数の値を引き出すことができます。

2.Structs

Go言語では、構造体を使用できます。
構造体とは、複数の異なるデータ型の変数(フィールド)をひとつにまとめたものです。

今回のサンプルコードは次のようになります。

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    fmt.Println(Vertex{1, 2}) //{1 2}
}

Vertexで変数をひとつにまとめます。メイン関数でVertex{1, 2}を使って変数XYフィールドを初期化します。

Struct Fields

structのフィールドは、ドットを用いてアクセスができます。

今回のサンプルコードは次のようになります。

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)    //4
}

初期化までは、先ほどと同じですが、メイン関数にあるように、v.X = 4構造体のXへ4が代入されて出力されています。

Pointers to structs

Go言語の構造体は、ポインタを通して変数へアクセスする方法を紹介します。

今回のサンプルコードは次のようになります。

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}
a
func main() {
    v := Vertex{1, 2}
    p := &v
    p.X = 1e9
    fmt.Println(v) //{1000000000 2}
}

main関数内にあるように、&を使って構造体のアドレスを与えることで、ポインタを通してアクセスできます。
フィールドXにアクセスするには(*p).Xのように書くことができます。 この表記法は面倒ですなので、Go言語では代わりに、p.Xと書くことができます。

Struct Literals

構造体の初期値の割り当て方法を紹介します。

今回のサンプルコードは次のようになります。

package main

import "fmt"

type Vertex struct {
    X, Y int
}

var (
    v1 = Vertex{1, 2}  
    v2 = Vertex{X: 1}  
    v3 = Vertex{}      
    p  = &Vertex{1, 2} 
)

func main() {
    fmt.Println(v1, p, v2, v3) //{1 2} &{1 2} {1 0} {0 0}
}

今までの初期値の割り当て方法は、フィールドの順番通り入力をしていましたが、Vertex{X: 1}とすることでXのみに代入ができます。
そのため、出力結果は{1 2} &{1 2} {1 0} {0 0}となります。

Arrays

次は、Go言語における配列について説明をします。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)

    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)
}

[n]T型は、型Tn個の変数の配列を表します。
次は、intの10個の配列を宣言しています。
var a [10]int
また、配列のサイズを変えることはできないです。

Slives

配列より柔軟なスライスについて紹介します。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13}

    var s []int = primes[1:4]
    fmt.Println(s) //[3 5 7]
}

配列は、サイズを変えることができないという課題がありましたが、スライスを使用することで可変長な配列を作ることができます。しかし、スライスと配列は別のものです。

今回は、primesという配列からスライスを作成しています。
a[low : high]のようにlowとhighを使用してスライス範囲を定義することで、スライスを作成できます。lowは範囲の下限のindexを指定して、highは範囲の上限のindex-1を指定します。

サンプルコードにおけるvar s [] int = primes[1:4]になります。

Slices are like references to arrays

スライスは、配列への参照のようなものですそれについて紹介します。

サンプルコードは次のようになります。

package main

import "fmt"

func main() {
    names := [4]string{
        "John",
        "Paul",
        "George",
        "Ringo",
    }
    fmt.Println(names) // [John Paul George Ringo]

    a := names[0:2]
    b := names[1:3]
    fmt.Println(a, b) // [John Paul] [Paul George]

    b[0] = "XXX"
    fmt.Println(a, b) // [John XXX] [XXX George]
    fmt.Println(names) // [John XXX George Ringo]
}

a := names[0:2]b := names[1:3]でスライスを作成しています。ここでb[0] = "XXX"このように配列を変更すると、出力結果が[John XXX] [XXX George]となります。
なぜかというとスライスは、その配列のアドレスが保存されているので、元の配列を変更してしまうと、スライスの値も変更されます。

Slice defaults

スライスを作成するときに既定値を使用する方法を説明します。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}

    s = s[1:4]
    fmt.Println(s) // [3 5 7]

    s = s[:2]
    fmt.Println(s) // [3 5]

    s = s[1:]
    fmt.Println(s) // [5]
}

今までのスライスの作成方法は、配列のインデックスを使用していました。
しかし、既定値という値を使用することで上限または、下限を省略できます。
サンプルコードにおいて、s = s[:2]s = s[1:]が既定値を使用した表現です。

s = s[1:4]は配列s{3,5,7}がスライスとなります。

s = s[:2]は、先ほど作成したスライスから再度スライスを作成します。既定値を使うことで、下限と上限を省略できます。なのでs = s[:2]は、s = s[0:2]と等価になり、{3,5}のスライスが作成されます。

s = s[1:]は上限の既定値を使用しています。そのため、s = s[1:2]と等価になり、{5}のスライスが作成されます。

Slice length and capacity

次は、スライスの長さと容量を取得する方法について説明します。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s) // len=6 cap=6 [2 3 5 7 11 13]

    s = s[:0]
    printSlice(s) // len=0 cap=6 []

    s = s[:4]
    printSlice(s) // len=4 cap=6 [2 3 5 7]

    s = s[2:]
    printSlice(s) // len=2 cap=4 [5 7]
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

スライスの長さ(len),容量(cap)を調べることができます。長さは、スライスの要素数のこと指し、容量は、元配列の要素数のことを指します。長さはlen(変数名)を使用し、容量はcap(変数名)を使用します。
そのためprintSlice関数では、長さと容量を表示させる関数となっています。

Nil slices

スライスにおけるゼロ値について説明をします。
スライスにおけるゼロ値は、nilです。nilのスライスは、長さは0であり容量も0となっています。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {
        fmt.Println("nil!")
    }
}
// 出力
// [] 0 0
// nil!

Creating a slice with make

今までとは違う、スライスの作成方法を紹介します。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    a := make([]int, 5)
    printSlice("a", a) // a len=5 cap=5 [0 0 0 0 0]

    b := make([]int, 0, 5)
    printSlice("b", b) // b len=0 cap=5 []

    c := b[:2]
    printSlice("c", c) // c len=2 cap=5 [0 0]

    d := c[2:5]
    printSlice("d", d) // d len=3 cap=3 [0 0 0]
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

今までのスライスの作成方法は配列を使っていましたが、make関数を使用することで、配列を作成してスライスを作成できます。

make([]int, 5)このようにすると、int型の長さが5、サイズが5のスライスを作成できます。
make([]int, 0, 5)このようにすると、int型の長さが0、サイズが5のスライスを作成できます。

Appending to a slice

スライスに新しい要素を追加する方法を紹介します。

今回のサンプルコードは次のようになります。

package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

    // append works on nil slices.
    s = append(s, 0)
    printSlice(s)

    // The slice grows as needed.
    s = append(s, 1)
    printSlice(s)

    // We can add more than one element at a time.
    s = append(s, 2, 3, 4)
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

// 出力
// X _ X
// O _ X
// _ _ O

スライスに新しい要素を追加するにはappendを使用します。スライス型の変数sappendする場合引数は、[s, 2, 3, 4]のようにします。第一引数に変数名、次からは、追加したい要素をカンマでくぎり入力します。

たとえば、appendした場合、サイズがスライスの容量より大きくなる場合は、より大きいサイズの配列を割り当て直します。
その場合、戻り値となるスライスは、新しい割当先を示すようになります。

おわりに

本記事では、Go言語のポインタ・構造体・スライスについて説明しました。次回もポインタ・構造体・スライスについて説明をします。