My Note

自己理解のためのブログ

Goの文法学習 ( 自分用Memoで適宜更新 )

先人たちのブログや技術書を活用して自分用の備忘録です。 参照した記事は参照に一覧で表示させていただきます。

( 適宜更新 )

Map

blog.golang.org

mapを作成

  • make
    • make(map[<キーの型>]<値の型>) で mapを生成
  • mapから値を取得
package main

import (
    "fmt"
)

func main() {
    m := make(map[string]int)
    m["test1"] = 100
    m["test2"] = 90

    fmt.Println(m["test1"])
    fmt.Println(m)
}

/* 結果
100
map[test1:100 test2:90]
*/

mapリテラルをつくる

※)リテラル

数値や文字列を直接に記述した定数のこと

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "test1": 100,
        "test2": 95,
        "test3": 50,
    }
    fmt.Println(m)
}

/* 結果
map[test1:100 test2:95 test3:50]
*/

値チェック

valueが存在するかを確認する。結果は true, false が返る

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "test1": 100,
        "test2": 95,
        "test3": 50,
    }

    val, chk := m["test1"]
    fmt.Println(val, chk)

    val, chk = m["test100"]
    fmt.Println(val, chk)
}

/* 結果
100 true
0 false
*/

key/valueを削除

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "test1": 100,
        "test2": 95,
        "test3": 50,
    }

    fmt.Println(m)
    delete(m, "test1")
    fmt.Println(m)
}

/* 結果
map[test1:100 test2:95 test3:50]
map[test2:95 test3:50]
*/

key/valueの全取得と要素数の取得

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "test1": 100,
        "test2": 95,
        "test3": 50,
    }

    for key, val := range m {
        fmt.Println(key, val)
    }
    fmt.Println(len(m))
}

/* 結果
test1 100
test2 95
test3 50
3
*/

error

package main

import (
    "fmt"
)

func main() {
    if err := doError();err != nil {
        fmt.Println(err)
        //return
    }
    fmt.Println("end of code")
}
func doError() error {
    message := "error occurred!!!"
    return fmt.Errorf(message)

}

/* 結果 ( return無し )
error occurred!!!
end of code
*/

/* 結果 ( return有り )
error occurred!!!
*/

Slice (スライス)

  • 特徴
    • 可変長配列
    • 配列全体のポインタ(ptr) / 配列の長さ(len) / 配列の容量(cap)からなるデータ構造

make()関数

  • make(型, 要素数, 容量) で定義する。
    • スライスが初期化され、デフォルトでは0が格納される
    • 容量は省略可能。省略すると 要素数容量 は同じになる
    • 組み込み関数
package main

import (
    "fmt"
)

func main() {
    test1 := make([]int, 5, 10)
    fmt.Println(test1)
    fmt.Println(len(test1))
    fmt.Println(cap(test1))
 
    fmt.Println()
 
    test2 := make([]int, 10)
    fmt.Println(test2)
    fmt.Println(len(test2))
    fmt.Println(cap(test2))    
}

/* 結果
[0 0 0 0 0]
5
10

[0 0 0 0 0 0 0 0 0 0]
10
10
*/

append()関数

  • append(slice, val1, val2, val3, ...) と使う
    • slice の末尾に val1, val2 ... と追加されていく
    • sliceの要素数は自動的に増える
    • 容量に達すると、容量の2倍を確保してくれる
    • 配列のメモリアドレスは容量が確保されると変わる

copy関数

  • copy(コピー先, コピー元) として使う
package main

import (
    "fmt"
)

func main() {
    test1 := []int{1, 2, 3, 4, 5}
    test2 := make([]int, 5)
 
    copy(test2, test1) 
    fmt.Println(test2)
}

/* 結果
[1 2 3 4 5]
*/

構造体/JSON

構造体の初期化

package main

import (
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    var p1 Person
    p1.Name = "yhidetoshi"
    p1.Age = 20
    fmt.Println("p1: "+p1.Name, p1.Age)

    p2 := Person{"yhidetoshi", 21}
    fmt.Println("p2: "+p2.Name, p2.Age)

    p3 := Person{Name: "yhidetoshi", Age: 22}
    fmt.Println("p3: "+p3.Name, p3.Age)

    p4 := &Person{Name: "yhidetoshi", Age: 23}
    fmt.Println("p4: "+p4.Name, p4.Age)

    p5 := new(Person)
    p5.Name = "yhidetoshi"
    p5.Age = 24
    fmt.Println("p5: "+p5.Name, p5.Age)

    p6 := &Person{}
    p6.Name = "yhidetoshi"
    p6.Age = 25
    fmt.Println("p6: "+p6.Name, p6.Age)

}

/* 結果
p1: yhidetoshi 20
p2: yhidetoshi 21
p3: yhidetoshi 22
p4: yhidetoshi 23
p5: yhidetoshi 24
p6: yhidetoshi 25
 */

構造体配列

フェレットを飼っている人には見やすいかもしれません。 フェレットの情報を保持した構造体を配列にして、3匹のフェレット情報を格納した例です。

package main

import "fmt"

type Ferret struct {
    Age   int
    Color string
    Farm  string
}

type Ferrets []Ferret

func main() {

    maru := Ferret{
        Age:   4,
        Color: "blackself",
        Farm:  "furFarm",
    }

    hana := Ferret{
        Age:   6,
        Color: "sable",
        Farm:  "ruby",
    }

    natu := Ferret{
        Age:   3,
        Color: "butterscotch",
        Farm:  "mountainDew",
    }

    var ferrets Ferrets
    ferrets = append(ferrets, maru)
    ferrets = append(ferrets, hana)
    ferrets = append(ferrets, natu)

    fmt.Println(ferrets)
}

/*
[{4 blackself furFarm} {6 sable ruby} {3 butterscotch mountainDew}]
*/

ネスト構造体

package main

import (
    "fmt"
)

type Ferret struct {
    Name string
    Food Food
}

type Food struct {
    Name   string
    Volume int
}

func main() {

    ferret := Ferret{
        Name: "maru",
        Food: Food{
            Name:   "zupurimeGrainFreeDiet",
            Volume: 5,
        },
    }

    fmt.Println(ferret.Name)
    fmt.Println(ferret.Food.Name)
    fmt.Println(ferret.Food.Volume)

}

/* 実行結果
maru
zupurimeGrainFreeDiet
5
*/

ネスト構造の実用例

  • 別ブログでこのネスト構造体を利用しました。

yhidetoshi.hatenablog.com

GuardDutyのjson形式が以下で、 Resourceがネストし、さらに InstanceDetails がネストしています。

{
    "schemaVersion": "2.0",
    "accountId": "XXX",
    "region": "ap-northeast-1",
    "type": "UnauthorizedAccess:EC2/TorRelay",
    "resource": {
        "resourceType": "Instance",
        "instanceDetails": {
            "instanceId": "i-99999999",
            "instanceType": "m3.xlarge"
        }
    },
    "title": "EC2 instance i-99999999 is communicating with Tor Exit node.",
    "description": "EC2 instance i-99999999 is communicating with IP address 198.51.100.0 on the Tor Anonymizing Proxy network."
}

なので、構造体の宣言は以下のようにしました。

  • go( 一部抜粋 )
// GuardDutyFindings set guardduty GuardDutyFindingsValue
type GuardDutyFindings struct {
    AccountID   string      `json:"accountId"`
    Region      string      `json:"region"`
    Type        string      `json:"type"`
    Severity    json.Number `json:"severity"`
    Title       string      `json:"title"`
    Description string      `json:"description"`
    Resource    Resource    `json:"resource"`
}

// Resource set guardduty ResourceValue
type Resource struct {
    ResourceType     string           `json:"resourceType,omitempty"`
    UserName         string           `json:"userName,omitempty"`
    InstanceDetails  InstanceDetails  `json:"instanceDetails,,omitempty"`
    AccessKeyDetails AccessKeyDetails `json:"accessKeyDetails,,omitempty"`
}

// InstanceDetails set guardduty value
type InstanceDetails struct {
    InstanceID   string `json:"instanceId,omitempty"`
    InstanceType string `json:"instanceType,"`
}

Marshalを使う

  • JSONを出力する
    • 構造体をJSON文字列に変換で Marshal を利用
    • インデントを付きで出力するために MarshalIndentを利用
    b, _ := json.Marshal(&ferret)
    fmt.Println(string(b))

    fmt.Println()

    bmi, _ := json.MarshalIndent(&ferret, "", "   ")
    fmt.Println(string(bmi))
  • 実行結果
{"Name":"maru","Food":{"Name":"zupurimeGrainFreeDiet","Volume":5}}

{
    "Name": "maru",
    "Food": {
        "Name": "zupurimeGrainFreeDiet",
        "Volume": 5
    }
}

Unmarshalを使う

  • JSONから構造体にパースする
  • JSON(in bytes)をGo Objectに変換する

  • ferret.json

[
  {
    "Name": "maru",
    "Food": {
      "Name": "zupurimeGrainFreeDiet",
      "Volume": 5
    }
  },
  {
    "Name": "hana",
    "Food": {
      "Name": "ferretSelectionSenior",
      "Volume": 3
    }
  },
  {
    "Name": "natu",
    "Food": {
      "Name": "totalyComplete",
      "Volume": 7
    }
  }
]
  • main.go
package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
)

type Ferret struct {
    Name string
    Food Food
}

type Food struct {
    Name   string
    Volume int
}

func main() {
    var ferrets []Ferret

    bytes, err := ioutil.ReadFile("ferret.json")
    if err != nil {
        fmt.Println(err)
    }

    errJson := json.Unmarshal(bytes, &ferrets)
    if errJson != nil {
        fmt.Println(errJson)
    }

    for _, value := range ferrets {
        fmt.Printf("%s\t%s\t%d\n", value.Name, value.Food.Name, value.Food.Volume)
    }
}

/* 実行結果
maru   zupurimeGrainFreeDiet   5
hana   ferretSelectionSenior   3
natu   totalyComplete  7
*/

NewDecoderを使う

  • ストリームから情報を読み込んだ時はjson.NewDecoderを使う
    • http.Getで返ったresp.Bodyなどの io.Reader型に対して使う
req, err := http.NewRequest("GET", "https://api.nature.global/1/devices", nil)
    if err != nil {
        fmt.Println("Error")
    }
    req.Header.Set("Authorization", "Bearer "+token)

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error request")
    }

    err = json.NewDecoder(resp.Body).Decode(&devices)
    if err != nil {
        fmt.Println("Error decode")
    }

詳細はこちら。 yhidetoshi.hatenablog.com

JSON-to-Go ( Convert JSON to Go struct )

JSON --> 構造体 してくれるサービスがる。

JSON-to-Go: Convert JSON to Go instantly

ポインタ

参考・引用について

このポインタのところは、以下の2つのブログで大変わかりやすく解説されています。自分が理解するために必要なところを引用・参照して記載しています。(多謝)

例え話をしないC言語のポインタの説明 | 右や左の旦那様

Goのポインタ - はじめてのGo言語

ポインタとは

  • 説明

    • int型変数のポインタの場合は *int と記述する
    • 変数のアドレス取得するときは & をつけ、アドレスから変数の中身へアクセスする時は * を使う
    • 変数 とは、メモリのアドレスに付けられた名前 のこと
    • 変数c があるのではなくて、 0x7fffffffe494 という場所 アドレス変数c という名前を付けている
    • コンパイラ変数cという場所 が必要なようだから、そのぶんメモリを用意しておこう。 0x7fffffffe494変数c と名付けよう」と決めている
    • コンパイラがこういったことを考えてくれるおかげで、われわれプログラマーは「変数c」というものの正体を深く考えずにプログラミングできる
    • ポインタは メモリ上の他のアドレスを指す変数
  • Pointerを使う理由

    • サイズの大きな変数を効率よく扱う
      • 関数の引数として値を渡す際に、「引数用のアドレス」に変数をコピーします。int型やchar型のような小さい変数であれば問題ないですが、大きなサイズの構造体を引数に取るような関数の場合、メモリコピーにCPUを使ってしまって性能が落ちてしまう。どれだけ大きな構造体でも、先頭のアドレスだけ渡してしまえば効率よくやり取りできる
void warizan(int warareru, int waru, int* syou, int* amari)
{
  *syou = warareru / waru;
  *amari = warareru % waru;
}

void main ()
{
  int a;
  int b;
  int c;
  int d;
  a = 17;
  b = 5;
  warizan(a, b, &c, &d);
  // c = 3, d = 2になる
}
  • 値渡し
    • ある変数を関数の引数として渡す場合、値のコピーが渡されます。そのため呼び出された関数内で変数の値を変更しても、元の値には影響がありません。これを「値渡し」と呼びます。
  • ポインタ渡し(参照渡し)
    • ポインタ変数を関数に渡した場合は、ポインタが指し示す値が、呼び出し元も、呼び出された方も同じものであるため、呼び出された関数内で元の値を変更することができます。これを「ポインタ渡し(または参照渡し)」と呼びます。

値渡しとポインタ渡しの簡単な例

package main

import "fmt"

func call(a int, b *int){
  a = a + 1
  *b = *b + 1
}

func main(){
  a,b := 5,5

  call(a, &b)
  fmt.Println("値渡し", a) // ---> 値渡し 5
  fmt.Println("ポインタ渡し", b) // ---> ポインタ渡し 6
}
  • ポインタ配列
    • Go 言語の配列名は配列の先頭アドレスを表していません。また、配列にアドレス演算子を適用すると、配列へのポインタが生成されますが、それは配列の先頭アドレスを表しているわけではありません。Go 言語の配列はひとつの「値」なので、配列へのポインタは配列そのものを指し示すことになります。

メソッドの ポインタレシーバ変数レシーバ

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}
  • ポイントレシーバ
    • 呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取得できる
var v Vertex
ScaleFunc(v)  // Compile error!
ScaleFunc(&v) // OK

var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK
  • 変数レシーバ
    • メソッドが変数レシーバである場合、呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取ることができる
var v Vertex
fmt.Println(AbsFunc(v))  // OK
fmt.Println(AbsFunc(&v)) // Compile error!

var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
  • ポインタレシーバを使う理由
    • メソッドがレシーバが指す先の変数を変更するため
    • メソッドの呼び出し毎に変数のコピーを避けるため。 例えば、レシーバが大きな構造体である場合に効率的

reflect

  • アサーション

    • interface{}型から具体的な型に落とし込む必要がある
    • アサーションする型が実体と合わない場合はpanicが発生する
  • reflectパッケージを利用する

    • アサーションする型が事前にわからない場合
      • mapは key/valueの両方の型を合わせるために難しい場合
package main

import (
    "fmt"
    "reflect"
)

func p(v ...interface{}) {
    fmt.Println(v...)
}

func main() {

    // 型チェック
    p(reflect.TypeOf(0)) // int

    //型比較
    p(reflect.TypeOf(0).Kind() == reflect.Int)     // true
    p(reflect.TypeOf(0) == reflect.TypeOf("hoge")) // false
    p(reflect.TypeOf(0) == reflect.TypeOf(1))      // true

    //reflectで値をセット

    /* Elem
   https://golang.org/pkg/reflect/#Value.Elem

   Elem returns the value that the interface v contains or that the pointer v points to.
   It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
   */

    var n int
    v := reflect.ValueOf(&n).Elem()

    v.SetInt(99)
    p(n) // 99

    v.Set(reflect.ValueOf(999))
    p(n) // 999

    /* Slice */

    // 型チェック
    p(reflect.TypeOf([]int{})) // []int

    // 型比較
    p(reflect.TypeOf([]int{}).Kind() == reflect.Slice)        // true
    p(reflect.TypeOf([]int{}) == reflect.TypeOf([]int{1, 2})) //true
}

テスト

書き方

  • 書き方のルール
    • テストコードは同一のディレクトリに配置する
    • テストコードのファイル名は _test.go とする --> calc_test.go
    • Testから始まる名前にする --> func TestSum(t *testing.T)
    • 引数は *testing.Tを渡す --> func TestSum(t *testing.T)

シンプルなコードで確認する。

.
├── calc
│   ├── calc.go
│   └── calc_test.go
└── main.go
  • main.go
package main

func main() {}
  • calc.go
package calc

func Sum(a, b int) int {
    return a + b
}
  • calc_test.go
package calc

import "testing"

func TestSum(t *testing.T) {
    if Sum(1 ,2) != 3{
        t.Fatal("Sum vaule should be 3, but not match" )
    }
}

実行方法

  • 実行のルール

    • go testを実行するとカレントディレクトリ以下になる *_test.goコンパイルされてテストが実行される
    • go test -v の(-v)オプションをつけるとテストケースの詳細を表示してくれる
    • _. からはじまるファイルは無視される
    • go test -run TestXXX のように -run正規表現によりテストケースを指定することができる
    • テスト結果はキャッシュされる
    • テスト結果のキャッシュを削除する --> go clean -testcache
  • $ go test -v ./calc

=== RUN   TestSum
--- PASS: TestSum (0.00s)
PASS
ok      _/Users/hidetoshi/TestGoStudy/calc  (cached)
  • $ go test -run TestSum ./calc
ok   _/Users/hidetoshi/TestGoStudy/calc  (cached)
  • $ go clean -testcache (キャッシュをクリア)

  • $ go test -run TestSum ./calc

ok   _/Users/hidetoshi/TestGoStudy/calc  0.008s

Tips

Goで開発するときに利用すべきツール

  • gofmt
  • goimports
    • パッケージを読み込むimport文の挿入ど削除を自動でおこなってくれる
  • govet
    • バグの原因になりそうなコードを検出してくれる
  • golint
    • Goらいしくない書き方を検出してくれる
    • -set_exit_status
      • 警告が発生したらエラーで終了させる場合に使う
    • -min_confidence
      • 厳しさを変更できる
        • デフォルト値は0.8
        • -min_confidence=0.5 のように指定する

Go Module

  • Go1.13から正式導入予定
  • 環境変数 export GO111MODULE=on
  • go mod init
    • go.modファイルが生成される
      • 依存関係の定義と管理のためのファイル
    • go: cannot determine module path for source directory... ( outside GOPATH, no import comments )
      • gitのリポジトリ設定がされていなければエラーになる ? と思う。
    • go getを実行するとgo.modが自動更新される。go.sumファイルが生成される
      • go.sumファイル
        • バージョンロックのためのファイル

表示形式

参考