概要

「処理が速い」「並行処理」が得意ということから、
今後大規模webサービスなどで重宝されると思いましたので、
学習していきたいと思います。

今回は、ポインタなどを見ていきたいと思います。

参考サイト

以下を実施してみました。

Welcome to a tour of Go

環境

GoogleChrome
※ブラウザ操作だけで学習できるため、 エディタを用意する必要がありません。
※私は、ローカルにCentOS8環境を用意して、チュートリアルを行います。
※Goインストール手順です。よろしければ、ご活用ください。
CentOS8へのGo言語インストール手順

ポインタ

ポインタは値のメモリアドレスを指します。

package main

import "fmt"

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

    //iのメモリアドレスを取得
    p := &i
    //「*」をつけることをポインタで取得した変数の値を出力
    fmt.Println(*p)
    //出力している値を上書き
    *p = 21
    //上書きしたため、iの値が変更されている
    fmt.Println(i)

    //jのメモリアドレスを取得
    p = &j
    //「*」をつけることをポインタで取得した変数の値を出力
    fmt.Println(*p)
    //出力している値を上書き
    *p = *p / 37
    //上書きしたため、jの値が変更されている
    fmt.Println(j)
}

//実行
go run pointers.go

//実行結果
42
21
2701
73

Structs

struct (構造体)は、フィールド( field )の集まりです。


package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

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

//実行
go run structs.go

//実行結果
{1 2}
    Struct Fields

structのフィールドは、ドット( . )を用いてアクセスする。


package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    //Xフィールドへアクセスし、代入
    v.X = 4
    //代入した値が反映されている
    fmt.Println(v.X)
}

//実行
go run struct-fields.go

//実行結果
4
    Pointers to structs

structのフィールドは、structのポインタを通してアクセスすることもできます。

フィールドXを持つstructのポインタ p がある場合、フィールド X にアクセスするには (*p).X のように書くことができます。
しかし、この表記法は大変面倒ですので、Goでは代わりに p.X と書くこともできます。


package main

import "fmt"

type Vertex struct {
        X int
        Y int
}

func main() {
        v := Vertex{1, 2}
        //structsをポインタに代入
        p := &v
        //structsのXフィールドに値を代入
        p.X = 1e9
        fmt.Println(v)
}

structリテラル

structリテラルは、フィールドの値を代入することで新しいstructの初期値の割り当てを行っている。

Name: 構文を使って、フィールドの一部だけを列挙することができます
(この方法でのフィールドの指定順序は関係ありません)。
※例: X: 1 として X だけを初期化など

& を頭に付けると、新しく割り当てられたstructへのポインタを戻します。


package main

import "fmt"

type Vertex struct {
        X, Y int
}

var (
        //値を代入することで新しいstructの初期値の割り当てを行う
        v1 = Vertex{1, 2}
        //Xだけ初期値割り当てを行う
        v2 = Vertex{X: 1}
        //初期化
        v3 = Vertex{}
        //&を頭に付けると、新しく割り当てられたstructへのポインタを戻す
        p  = &Vertex{1, 2}
)

func main() {
        fmt.Println(v1, p, v2, v3)
}

//実行
go run struct-literals.go

//実行結果
{1 2} &{1 2} {1 0} {0 0}

配列(Arrays)

[n]T型で表す。
T型の配列にn個まで変数を入れられるという意味になります。


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)
}

//実行
go run array.go

//実行結果
Hello World
[Hello World]
[2 3 5 7 11 13]

スライス(Slices)

スライスは可変長です。
より柔軟な配列と見なすこともできます。
実際には、スライスは配列よりもより一般的とのことです。

[]T型と表す。
T型のスライスという意味になります。

コロンで区切られた二つのインデックス low と high の
境界を指定することによってスライスが形成されます

例:a[low : high]


package main

import "fmt"

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

        //1番目の値から4番目の値の間を取得
        //1番目は含む・4番目は含まない
        var s []int = primes[1:4]
        fmt.Println(s)
}

//実行
go run slices.go

//実行結果
[3 5 7]
    スライスは配列への参照のようなもの

スライスはどんなデータも格納しておらず、
単に元の配列の部分列を指し示しています。

スライスの要素を変更すると、
その元となる配列の対応する要素が変更されます。

同じ元となる配列を共有している他のスライスは、
それらの変更が反映されます。


package main

import "fmt"

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

        //配列の中身をスライスに格納
        a := names[0:2]
        b := names[1:3]
        //格納されていることを確認
        fmt.Println(a, b)

        //スライスの中身を変更
        b[0] = "XXX"
        //変更した内容が配列にも反映されているか確認
        fmt.Println(a, b)
        fmt.Println(names)
}

//実行
go run slice-pointers.go

//実行結果
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
    スライスのリテラル(初期値割り振りの認識)

・配列リテラル
[3]bool{true, true, false}

・スライスリテラル
[]bool{true, true, false}


package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}

//実行
go run slice-literals.go

//実行結果
[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
    スライスの長さ( length )と容量( capacity )

スライスの長さ:スライスに含まれる要素の数。
スライスの容量:スライスの最初の要素から数えて、元となる配列の要素数。


package main

import "fmt"

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

        //スライスの長さを0にする
        s = s[:0]
        printSlice(s)

        //スライスの長さを伸ばす
        s = s[:4]
        printSlice(s)

        //スライスの値を削除
        s = s[2:]
        printSlice(s)
}

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

//実行
go run slice-len-cap.go

//実行結果
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
    スライスのゼロ値

スライスのゼロ値は nil です。
nil スライスは 0 の長さと容量を持っており、
何の元となる配列も持っていません。
※宣言するだけの時に使用する認識


package main

import "fmt"

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

//実行
go run nil-slices.go

//実行結果
[] 0 0
nil!
    スライスは、組み込みのmake関数を使用して作成

動的サイズの配列を作成する方法です。
make関数はゼロ化された配列を割り当て、その配列を指すスライスを返す。
※make関数が対応する型はslice,map,chanelのみ
※上記は、Goのnew関数とmake関数の違いを解説する参照


package main

import "fmt"

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

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

    c := b[:2]
    printSlice("c", c)

    d := c[2:5]
    printSlice("d", d)
}

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

//実行
go run making-slices.go

//実行結果
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
    Slices of slices

スライスは、他のスライスを含む任意の型を含むことができる。


package main

import (
    "fmt"
    "strings"
)

func main() {
    // Create a tic-tac-toe board.
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // The players take turns.
    board[0][0] = "X"
    board[2][2] = "O"
    board[1][2] = "X"
    board[1][0] = "O"
    board[0][2] = "X"

    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }
}

//実行
go run slices-of-slices.go

//実行結果
X _ X
O _ X
_ _ O
    スライスへ新しい要素を追加

Goの組み込みの append を使います。


package main

import "fmt"

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

        s = append(s, 0)
        printSlice(s)

        s = append(s, 1)
        printSlice(s)

        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)
}

//実行
go run append.go

//実行結果
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]

Range

for ループに利用する range は、スライスや、マップ( map )を
ひとつずつ反復処理するために使います。

スライスをrangeで繰り返す場合、rangeは反復毎に2つの変数を返します。
1つ目の変数はインデックス( index )で、2つ目はインデックスの場所の要素のコピーです。


package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main(){
        for i, v := range pow{
                fmt.Printf("2**%d = %d\n", i,v)
        }
}

//実行
go run range.go

//実行結果
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
    Range continued

インデックスや値は、 ” _ “(アンダーバー) へ代入することで捨てることができます。

※ソースは一部変更しております。


package main

import "fmt"

func main(){
        pow := make([]int, 10)

        for i := range pow {
                pow[i] = 1 << uint(i)
                fmt.Println(pow)
        }

        for _, value := range pow{
                fmt.Printf("%d\n", value)
        }
}

//実行
go run range-continued.go

//実行結果
[1 0 0 0 0 0 0 0 0 0]
[1 2 0 0 0 0 0 0 0 0]
[1 2 4 0 0 0 0 0 0 0]
[1 2 4 8 0 0 0 0 0 0]
[1 2 4 8 16 0 0 0 0 0]
[1 2 4 8 16 32 0 0 0 0]
[1 2 4 8 16 32 64 0 0 0]
[1 2 4 8 16 32 64 128 0 0]
[1 2 4 8 16 32 64 128 256 0]
[1 2 4 8 16 32 64 128 256 512]
1
2
4
8
16
32
64
128
256
512

Maps

map はキーと値とを関連付けます。

マップのゼロ値は nil です。 nil マップはキーを持っておらず、
またキーを追加することもできません。
make 関数は指定された型のマップを初期化して、使用可能な状態で返します。


package main

import "fmt"

type Vertex struct{
        Lat, Long float64
}

var m map[string]Vertex

func main(){
        m = make(map[string]Vertex)
        m["Bell Labs"]  = Vertex{
                40.68433, -74.39967,
        }
        fmt.Println(m["Bell Labs"])
}

//実行
go run maps.go

//実行結果
{40.68433 -74.39967}
    Map literals

mapリテラルは、structリテラルに似ていますが、 キー ( key )が必要です。


package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

func main() {
    fmt.Println(m)
}

//実行
go run map-literals.go

//実行結果
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
    Map literals continued

もし、mapに渡すトップレベルの型が単純な型名である場合は、
リテラルの要素から推定できますので、その型名を省略することができます。


package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

func main() {
    fmt.Println(m)
}

//実行
go run map-literals-continued.go

//実行結果
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
    Mapsの操作

■m へ要素(elem)の挿入や更新の場合
m[key] = elem

■要素の取得:
elem = m[key]

■要素の削除:
delete(m, key)

■キーに対する要素が存在するかどうか
※2つの目の値で確認します

elem, ok = m[key]
※もし、 m に key があれば、変数 ok は true となり、
存在しなければ、 ok は false となります。


package main

import "fmt"

func main() {
    m := make(map[string]int)

    //挿入
    m["Answer"] = 42
    fmt.Println("The value:", m["Answer"])

    //更新
    m["Answer"] = 48
    fmt.Println("The value:", m["Answer"])

    //削除
    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    //検索
    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
}

//実行
go run matating-maps.go

//実行結果
The value: 42
The value: 48
The value: 0
The value: 0 Present? false

関数(Function values)

関数も変数です。他の変数のように関数を渡すことができます。
関数値( function value )は、関数の引数に取ることもできますし、戻り値としても利用できます。


package main

import (
    "fmt"
    "math"
)

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}

//実行
go run function-values.go

//実行結果
13
5
81
    Function closures

Goの関数は クロージャ( closure ) です。
クロージャは、それ自身の外部から変数を参照する関数値です。
この関数は、参照された変数へアクセスして変えることができ、
その意味では、その関数は変数へ”バインド”( bind )されています。


package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

//実行
go run function-closures.go

//実行結果
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

まとめ

次回はメソッドなどを見ていきたいと思います。

广告
将在 10 秒后关闭
bannerAds