LoginSignup
1
1

More than 3 years have passed since last update.

Golang チュートリアル その3

Posted at

概要

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

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

参考サイト

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

Welcome to a tour of Go

環境

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

ポインタ

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

pointers.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 )の集まりです。

structs.go

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のフィールドは、ドット( . )を用いてアクセスする。

struct-fields.go

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 と書くこともできます。

struct-pointters.go

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へのポインタを戻します。

struct-literals.go

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個まで変数を入れられるという意味になります。

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

//実行
go run array.go

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

スライス(Slices)

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

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

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

例:a[low : high]

slices.go

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]
  • スライスは配列への参照のようなもの

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

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

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

slice-pointers.go

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}

slice-literals.go

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 )

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

slice-len-cap.go

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 の長さと容量を持っており、
何の元となる配列も持っていません。
※宣言するだけの時に使用する認識

nil-slices.go

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関数の違いを解説する参照

making-slices.go

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

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

slices-of-slices.go

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 を使います。

append.go

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つ目はインデックスの場所の要素のコピーです。

range.go

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

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

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

range-continued.go

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 関数は指定された型のマップを初期化して、使用可能な状態で返します。

maps.go

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 )が必要です。

map-literals.go

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に渡すトップレベルの型が単純な型名である場合は、
リテラルの要素から推定できますので、その型名を省略することができます。

map-literals-continued.go

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 となります。

mutating-maps.go

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 )は、関数の引数に取ることもできますし、戻り値としても利用できます。

function-values.go

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 )されています。

function-closures.go

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

まとめ

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1